added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package main;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.ICommunicatorBuilder;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.IParadoxCommunicator;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.ParadoxBuilderFactory;
|
||||
import org.openhab.binding.paradoxalarm.internal.model.PanelType;
|
||||
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Main} - used for testing purposes of low-level stuff.
|
||||
*
|
||||
* @author Konstantin Polihronov - Initial contribution
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(Main.class);
|
||||
|
||||
private static String ipAddress;
|
||||
private static int port;
|
||||
|
||||
// PASSWORD is your IP150 password
|
||||
private static String ip150Password;
|
||||
|
||||
// PC Password is the value of section 3012, i.e. if value is 0987, PC Password is two bytes 0x09, 0x87
|
||||
private static String pcPassword;
|
||||
|
||||
private static ScheduledExecutorService scheduler;
|
||||
|
||||
private static IParadoxCommunicator communicator;
|
||||
|
||||
public static void main(String[] args) {
|
||||
readArguments(args);
|
||||
|
||||
try {
|
||||
scheduler = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
|
||||
logger.info("System properties={}",
|
||||
System.getProperties().get(org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY));
|
||||
|
||||
ParadoxBuilderFactory factory = new ParadoxBuilderFactory();
|
||||
ICommunicatorBuilder builder = factory.createBuilder(PanelType.EVO192);
|
||||
communicator = builder.withIp150Password(ip150Password).withPcPassword(pcPassword).withIpAddress(ipAddress)
|
||||
.withTcpPort(port).withMaxPartitions(4).withMaxZones(20).withScheduler(scheduler)
|
||||
.withEncryption(true).build();
|
||||
|
||||
ParadoxPanel panel = ParadoxPanel.getInstance();
|
||||
panel.setCommunicator(communicator);
|
||||
communicator.setListeners(Arrays.asList(panel));
|
||||
|
||||
communicator.startLoginSequence();
|
||||
|
||||
scheduler.scheduleWithFixedDelay(() -> {
|
||||
refreshMemoryMap(communicator, false);
|
||||
}, 7, 5, TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
logger.error("Exception: ", e);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void refreshMemoryMap(IParadoxCommunicator communicator, boolean withEpromValues) {
|
||||
logger.debug("Refreshing memory map");
|
||||
communicator.refreshMemoryMap();
|
||||
ParadoxPanel panel = ParadoxPanel.getInstance();
|
||||
panel.getPartitions().stream().forEach(partition -> logger.debug("Partition={}", partition));
|
||||
panel.getZones().stream().filter(zone -> zone.getId() == 19).forEach(zone -> logger.debug("Zone={}", zone));
|
||||
logger.debug("PanelTime={}, ACLevel={}, DCLevel={}, BatteryLevel={}", panel.getPanelTime(), panel.getVdcLevel(),
|
||||
panel.getDcLevel(), panel.getBatteryLevel());
|
||||
}
|
||||
|
||||
private static void readArguments(String[] args) {
|
||||
MainMethodArgParser parser = new MainMethodArgParser(args);
|
||||
logger.info("Arguments retrieved successfully from CLI.");
|
||||
|
||||
ip150Password = parser.getPassword();
|
||||
logger.info("IP150 Password: {}", ip150Password);
|
||||
|
||||
pcPassword = parser.getPcPassword();
|
||||
logger.info("PC Password: {}", pcPassword);
|
||||
|
||||
ipAddress = parser.getIpAddress();
|
||||
logger.info("IP150 IP Address: {}", ipAddress);
|
||||
|
||||
port = new Integer(parser.getPort());
|
||||
logger.info("IP150 port: {}", port);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package main;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MainMethodArgParser} Helper class to parse the arguments of main method, which is used for direct
|
||||
* communication testing.
|
||||
*
|
||||
* @author Konstantin Polihronov - Initial contribution
|
||||
*/
|
||||
public class MainMethodArgParser {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MainMethodArgParser.class);
|
||||
|
||||
private static final int NUMBER_OF_ARGUMENTS = 8;
|
||||
private static final String USAGE_TEXT = "Usage: application --password <YOUR_PASSWORD_FOR_IP150> --pc_password <your PC_password> --ip_address <address of IP150> --port <port of Paradox>\n (pc password default is 0000, can be obtained by checking section 3012), default port is 10000";
|
||||
|
||||
private static final String KEY_PREFIX = "--";
|
||||
|
||||
private static final String PASSWORD_KEY = "--password";
|
||||
private static final String PC_PASSWORD_KEY = "--pc_password";
|
||||
private static final String IP_ADDRESS_KEY = "--ip_address";
|
||||
private static final String PORT_KEY = "--port";
|
||||
private static final List<String> ARGUMENT_KEYS = Arrays.asList(PASSWORD_KEY, PC_PASSWORD_KEY, IP_ADDRESS_KEY,
|
||||
PORT_KEY);
|
||||
|
||||
private Map<String, String> argumentsMap;
|
||||
|
||||
public MainMethodArgParser(String[] args) {
|
||||
this.argumentsMap = parseArguments(args);
|
||||
validateArguments();
|
||||
}
|
||||
|
||||
private Map<String, String> parseArguments(String[] args) {
|
||||
if (args == null || args.length < NUMBER_OF_ARGUMENTS) {
|
||||
logger.error(USAGE_TEXT);
|
||||
throw new IllegalArgumentException("Arguments= " + Arrays.asList(args));
|
||||
}
|
||||
|
||||
Map<String, String> result = new HashMap<>();
|
||||
for (int i = 0; i < args.length;) {
|
||||
if (!args[i].startsWith(KEY_PREFIX)) {
|
||||
throw new IllegalArgumentException("Argument " + args[i] + " does not start with --");
|
||||
}
|
||||
String key = args[i];
|
||||
String value;
|
||||
if (args[i + 1] != null && args[i + 1].startsWith(KEY_PREFIX)) {
|
||||
value = null;
|
||||
i++;
|
||||
} else {
|
||||
value = args[i + 1];
|
||||
i += 2;
|
||||
}
|
||||
result.put(key, value);
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void validateArguments() {
|
||||
for (String argKey : ARGUMENT_KEYS) {
|
||||
String value = argumentsMap.get(argKey);
|
||||
if (value == null) {
|
||||
logger.error(USAGE_TEXT);
|
||||
throw new IllegalArgumentException("Argument " + argKey + "is mandatory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return argumentsMap.get(PASSWORD_KEY);
|
||||
}
|
||||
|
||||
public String getPcPassword() {
|
||||
return argumentsMap.get(PC_PASSWORD_KEY);
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return argumentsMap.get(IP_ADDRESS_KEY);
|
||||
}
|
||||
|
||||
public String getPort() {
|
||||
return argumentsMap.get(PORT_KEY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.slf4j.simpleLogger.defaultLogLevel=trace
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package tests;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.messages.CommandPayload;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.messages.PartitionCommand;
|
||||
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
|
||||
|
||||
/**
|
||||
* The {@link TestCreateCommandPayload} This test tests the proper build of command payload object for partition entity
|
||||
* with all types of commands.
|
||||
*
|
||||
* @author Konstantin Polihronov - Initial contribution
|
||||
*/
|
||||
public class TestCreateCommandPayload {
|
||||
|
||||
@Test
|
||||
public void testCreatePayload() {
|
||||
for (PartitionCommand command : PartitionCommand.values()) {
|
||||
for (int partitionNumber = 1; partitionNumber <= 8; partitionNumber++) {
|
||||
CommandPayload payload = new CommandPayload(partitionNumber, command);
|
||||
assertNibble(partitionNumber, command, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNibble(int partitionNumber, PartitionCommand command, CommandPayload payload) {
|
||||
byte[] bytes = payload.getBytes();
|
||||
int payloadIndexOfByteToCheck = 6 + (partitionNumber - 1) / 2;
|
||||
byte byteValue = bytes[payloadIndexOfByteToCheck];
|
||||
if ((partitionNumber - 1) % 2 == 0) {
|
||||
Assert.assertTrue(ParadoxUtil.getHighNibble(byteValue) == command.getCommand());
|
||||
} else {
|
||||
Assert.assertTrue(ParadoxUtil.getLowNibble(byteValue) == command.getCommand());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package tests;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.crypto.EncryptionHandler;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderCommand;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
|
||||
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
|
||||
|
||||
/**
|
||||
* The {@link TestEncryptionHandler} This test tests various functions from ParadoxUtils class
|
||||
*
|
||||
* @author Konstantin Polihronov - Initial contribution
|
||||
*/
|
||||
public class TestEncryptionHandler {
|
||||
|
||||
private static final String INPUT_STRING = "My test string for encryption.";
|
||||
private static final String KEY = "MyKeyToEncrypt";
|
||||
|
||||
@Test
|
||||
public void testEncryptDecryptString() {
|
||||
EncryptionHandler.getInstance().updateKey(ParadoxUtil.getBytesFromString(KEY));
|
||||
|
||||
byte[] originalBytes = ParadoxUtil.getBytesFromString(INPUT_STRING);
|
||||
ParadoxUtil.printByteArray("Original=", originalBytes);
|
||||
|
||||
byte[] encrypted = EncryptionHandler.getInstance().encrypt(originalBytes);
|
||||
assertNotEquals(originalBytes, encrypted);
|
||||
|
||||
byte[] decrypted = EncryptionHandler.getInstance().decrypt(encrypted);
|
||||
byte[] result = decrypted.length != originalBytes.length ? Arrays.copyOf(decrypted, originalBytes.length)
|
||||
: decrypted;
|
||||
ParadoxUtil.printByteArray("Result=", result);
|
||||
assertEquals(originalBytes.length, result.length);
|
||||
|
||||
assertEquals(INPUT_STRING, new String(result));
|
||||
}
|
||||
|
||||
private static final byte[] ENCRYPTION_KEY_BYTES = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 };
|
||||
private static final byte[] ENCRYPTED_EXPECTED2 = { (byte) 0xAA, 0x0A, 0x00, 0x03, 0x09, (byte) 0xF0, 0x00, 0x00,
|
||||
0x01, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE,
|
||||
(byte) 0xF9, 0x11, 0x5A, (byte) 0xD7, 0x7C, (byte) 0xCB, (byte) 0xF4, 0x75, (byte) 0xB0, 0x49, (byte) 0xC3,
|
||||
0x11, 0x1A, 0x41, (byte) 0x94, (byte) 0xE0 };
|
||||
|
||||
@Test
|
||||
public void testCreateAndEncryptStartingPacket() {
|
||||
ParadoxIPPacket paradoxIPPacket = new ParadoxIPPacket(ENCRYPTION_KEY_BYTES, false)
|
||||
.setCommand(HeaderCommand.CONNECT_TO_IP_MODULE);
|
||||
|
||||
EncryptionHandler.getInstance().updateKey(ENCRYPTION_KEY_BYTES);
|
||||
paradoxIPPacket.encrypt();
|
||||
|
||||
final byte[] packetBytes = paradoxIPPacket.getBytes();
|
||||
ParadoxUtil.printByteArray("Expected=", ENCRYPTED_EXPECTED2);
|
||||
ParadoxUtil.printByteArray("Packet= ", packetBytes);
|
||||
|
||||
Assert.assertTrue(Arrays.equals(packetBytes, ENCRYPTED_EXPECTED2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package tests;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.messages.CommandPayload;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.messages.EpromRequestPayload;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderCommand;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.messages.IPayload;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
|
||||
import org.openhab.binding.paradoxalarm.internal.communication.messages.PartitionCommand;
|
||||
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxException;
|
||||
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link TestGetBytes} This test tests creation of IP packet and it's getBytes() method
|
||||
*
|
||||
* @author Konstantin Polihronov - Initial contribution
|
||||
*/
|
||||
public class TestGetBytes {
|
||||
|
||||
private static final int PARTITION_NUMBER = 1;
|
||||
|
||||
static {
|
||||
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "TRACE");
|
||||
}
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ParadoxUtil.class);
|
||||
|
||||
private static final byte[] EXPECTED1 = { (byte) 0xAA, 0x0A, 0x00, 0x03, 0x08, (byte) 0xF0, 0x00, 0x00, 0x01,
|
||||
(byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, 0x01, 0x02, 0x03,
|
||||
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 };
|
||||
|
||||
private static final byte[] PAYLOAD = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 };
|
||||
|
||||
@Test
|
||||
public void testParadoxIPPacket() {
|
||||
ParadoxIPPacket paradoxIPPacket = new ParadoxIPPacket(PAYLOAD, false)
|
||||
.setCommand(HeaderCommand.CONNECT_TO_IP_MODULE);
|
||||
final byte[] packetBytes = paradoxIPPacket.getBytes();
|
||||
|
||||
ParadoxUtil.printByteArray("Expected =", EXPECTED1);
|
||||
ParadoxUtil.printByteArray("Packet =", packetBytes);
|
||||
|
||||
Assert.assertTrue(Arrays.equals(packetBytes, EXPECTED1));
|
||||
}
|
||||
|
||||
private static final byte[] EXPECTED_COMMAND_PAYLOAD = { 0x40, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
@Test
|
||||
public void testCommandPayload() {
|
||||
CommandPayload payload = new CommandPayload(PARTITION_NUMBER, PartitionCommand.ARM);
|
||||
final byte[] packetBytes = payload.getBytes();
|
||||
|
||||
ParadoxUtil.printByteArray("Expected =", EXPECTED_COMMAND_PAYLOAD);
|
||||
ParadoxUtil.printByteArray("Result =", packetBytes);
|
||||
|
||||
Assert.assertTrue(Arrays.equals(packetBytes, EXPECTED_COMMAND_PAYLOAD));
|
||||
}
|
||||
|
||||
private static final byte[] EXPECTED_MEMORY_PAYLOAD = { (byte) 0xAA, 0x0A, 0x00, 0x03, 0x08, (byte) 0xF0, 0x00,
|
||||
0x00, 0x01, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, 0x01,
|
||||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 };
|
||||
|
||||
@Test
|
||||
public void testMemoryRequestPayload() throws ParadoxException {
|
||||
int address = 0x3A6B + (PARTITION_NUMBER) * 107;
|
||||
byte labelLength = 16;
|
||||
IPayload payload = new EpromRequestPayload(address, labelLength);
|
||||
byte[] bytes = payload.getBytes();
|
||||
ParadoxUtil.printByteArray("Expected =", EXPECTED_MEMORY_PAYLOAD);
|
||||
ParadoxUtil.printByteArray("Result =", bytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package tests;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
|
||||
|
||||
/**
|
||||
* The {@link TestParadoxUtil} This test tests various functions from ParadoxUtils class
|
||||
*
|
||||
* @author Konstantin Polihronov - Initial contribution
|
||||
*/
|
||||
public class TestParadoxUtil {
|
||||
|
||||
@Test
|
||||
public void testExtendArray() {
|
||||
byte[] arrayToExtend = { 0x0A, 0x50, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x59 };
|
||||
final int rate = 16;
|
||||
byte[] extendedArray = ParadoxUtil.extendArray(arrayToExtend, rate);
|
||||
|
||||
final byte[] EXPECTED_RESULT = { 0x0A, 0x50, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x59, (byte) 0xEE, (byte) 0xEE,
|
||||
(byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE };
|
||||
Assert.assertArrayEquals(EXPECTED_RESULT, extendedArray); //
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeArrays() {
|
||||
final byte[] ARR1 = { 0x01, 0x02, 0x03 };
|
||||
final byte[] ARR2 = { 0x04, 0x05, 0x06 };
|
||||
final byte[] ARR3 = { 0x07, 0x08, 0x09 };
|
||||
byte[] mergedArrays = ParadoxUtil.mergeByteArrays(ARR1, ARR2, ARR3);
|
||||
|
||||
final byte[] EXPECTED_RESULT = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
|
||||
Assert.assertArrayEquals(EXPECTED_RESULT, mergedArrays);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user