implemented report repository
This commit is contained in:
parent
0276763a8d
commit
6b2a323a9c
@ -74,6 +74,7 @@ public abstract class HttpRepository extends Repository {
|
|||||||
|
|
||||||
private ReqResp<String> sendHttp(HttpRequest request) {
|
private ReqResp<String> sendHttp(HttpRequest request) {
|
||||||
try(HttpClient client = HttpClient.newHttpClient()) {
|
try(HttpClient client = HttpClient.newHttpClient()) {
|
||||||
|
this.validateThread();
|
||||||
HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
return new ReqResp<>(httpResponse.statusCode(), httpResponse.body());
|
return new ReqResp<>(httpResponse.statusCode(), httpResponse.body());
|
||||||
} catch(IOException | InterruptedException e) {
|
} catch(IOException | InterruptedException e) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package eu.mhsl.craftattack.spawn.api.client;
|
package eu.mhsl.craftattack.spawn.api.client;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import eu.mhsl.craftattack.spawn.Main;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
@ -12,4 +14,8 @@ public abstract class Repository {
|
|||||||
this.basePath = basePath;
|
this.basePath = basePath;
|
||||||
this.gson = new Gson();
|
this.gson = new Gson();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void validateThread() {
|
||||||
|
if(Bukkit.isPrimaryThread()) Main.logger().warning("Repository was called synchronously!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package eu.mhsl.craftattack.spawn.api.client.repositories;
|
|||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.api.client.HttpRepository;
|
import eu.mhsl.craftattack.spawn.api.client.HttpRepository;
|
||||||
import eu.mhsl.craftattack.spawn.api.client.ReqResp;
|
import eu.mhsl.craftattack.spawn.api.client.ReqResp;
|
||||||
import eu.mhsl.craftattack.spawn.appliances.report.Report;
|
|
||||||
import eu.mhsl.craftattack.spawn.util.api.ApiUtil;
|
import eu.mhsl.craftattack.spawn.util.api.ApiUtil;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -15,22 +14,44 @@ public class ReportRepository extends HttpRepository {
|
|||||||
super(ApiUtil.getBaseUri(), ApiUtil::withAuthorizationSecret);
|
super(ApiUtil.getBaseUri(), ApiUtil::withAuthorizationSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ReportData(@NotNull UUID reporter, @Nullable UUID reported, String reason) {
|
public record ReportCreationInfo(@NotNull UUID reporter, @Nullable UUID reported, String reason) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ReportDataResponse(@NotNull String url) {
|
public record ReportUrl(@NotNull String url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ReportsResponse(List<ReportInfo> from_self, Object to_self) {
|
public record PlayerReports(
|
||||||
|
List<Report> from_self,
|
||||||
|
Object to_self
|
||||||
|
) {
|
||||||
|
public record Report(
|
||||||
|
@Nullable Reporter reported,
|
||||||
|
@NotNull String subject,
|
||||||
|
boolean draft,
|
||||||
|
@NotNull String status,
|
||||||
|
@NotNull String url
|
||||||
|
) {
|
||||||
|
public record Reporter(
|
||||||
|
@NotNull String username,
|
||||||
|
@NotNull String uuid
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ReportInfo(Reporter reported, @NotNull String subject, boolean draft, @NotNull String status, @NotNull String url) {
|
public ReqResp<PlayerReports> queryReports(UUID player) {
|
||||||
|
return this.get(
|
||||||
|
"report",
|
||||||
|
(parameters) -> parameters.addParameter("uuid", player.toString()),
|
||||||
|
PlayerReports.class
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Reporter(@NotNull String username, @NotNull String uuid) {
|
public ReqResp<ReportUrl> createReport(ReportCreationInfo data) {
|
||||||
}
|
return this.post(
|
||||||
|
"report",
|
||||||
public ReqResp<ReportsResponse> queryReports(UUID player) {
|
data,
|
||||||
return this.get("reports", (builder) -> builder.addParameter("token", "asd"), input, SendReportResponse.class);
|
ReportUrl.class
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,13 @@ public class WhitelistRepository extends HttpRepository {
|
|||||||
String lastname,
|
String lastname,
|
||||||
Long banned_until,
|
Long banned_until,
|
||||||
Long outlawed_until
|
Long outlawed_until
|
||||||
) {}
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
public ReqResp<UserData> getUserData(UUID userId) {
|
public ReqResp<UserData> getUserData(UUID userId) {
|
||||||
return this.get(
|
return this.get(
|
||||||
"user",
|
"user",
|
||||||
uriBuilder -> uriBuilder.addParameter("uuid", userId.toString()),
|
parameters -> parameters.addParameter("uuid", userId.toString()),
|
||||||
UserData.class
|
UserData.class
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package eu.mhsl.craftattack.spawn.appliances.report;
|
package eu.mhsl.craftattack.spawn.appliances.report;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import eu.mhsl.craftattack.spawn.Main;
|
import eu.mhsl.craftattack.spawn.Main;
|
||||||
|
import eu.mhsl.craftattack.spawn.api.client.ReqResp;
|
||||||
|
import eu.mhsl.craftattack.spawn.api.client.repositories.ReportRepository;
|
||||||
import eu.mhsl.craftattack.spawn.appliance.Appliance;
|
import eu.mhsl.craftattack.spawn.appliance.Appliance;
|
||||||
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
|
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@ -10,22 +11,14 @@ import net.kyori.adventure.text.TextComponent;
|
|||||||
import net.kyori.adventure.text.event.ClickEvent;
|
import net.kyori.adventure.text.event.ClickEvent;
|
||||||
import net.kyori.adventure.text.event.HoverEvent;
|
import net.kyori.adventure.text.event.HoverEvent;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class Report extends Appliance {
|
public class Report extends Appliance {
|
||||||
public static Component helpText() {
|
public static Component helpText() {
|
||||||
@ -38,163 +31,51 @@ public class Report extends Appliance {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private URI apiEndpoint;
|
|
||||||
private String apiSecret;
|
|
||||||
|
|
||||||
public Report() {
|
public Report() {
|
||||||
super("report");
|
super("report");
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Request(@NotNull UUID reporter, @Nullable UUID reported, String reason) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private record ReportResponse(@NotNull String url) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private record ReportsResponse(List<ReportInfo> from_self, Object to_self) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private record ReportInfo(Reporter reported, @NotNull String subject, boolean draft, @NotNull String status, @NotNull String url) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private record Reporter(@NotNull String username, @NotNull String uuid) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reportToUnknown(@NotNull Player issuer) {
|
public void reportToUnknown(@NotNull Player issuer) {
|
||||||
Request request = new Request(issuer.getUniqueId(), null, "");
|
ReportRepository.ReportCreationInfo request = new ReportRepository.ReportCreationInfo(issuer.getUniqueId(), null, "");
|
||||||
this.issueReport(issuer, request);
|
this.createReport(issuer, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reportToKnown(@NotNull Player issuer, @NotNull String targetUsername, @Nullable String reason) {
|
public void reportToKnown(@NotNull Player issuer, @NotNull String targetUsername, @Nullable String reason) {
|
||||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(targetUsername);
|
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(targetUsername);
|
||||||
if(issuer.getUniqueId().equals(offlinePlayer.getUniqueId())) {
|
if(issuer.getUniqueId().equals(offlinePlayer.getUniqueId())) {
|
||||||
issuer.sendMessage(
|
issuer.sendMessage(Component.text("Du kannst dich nicht selbst reporten.", NamedTextColor.RED));
|
||||||
Component.text("Du kannst dich nicht selbst reporten.").color(NamedTextColor.RED)
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Request request = new Request(issuer.getUniqueId(), offlinePlayer.getUniqueId(), Optional.ofNullable(reason).orElse(""));
|
ReportRepository.ReportCreationInfo request = new ReportRepository.ReportCreationInfo(
|
||||||
this.issueReport(issuer, request);
|
issuer.getUniqueId(),
|
||||||
}
|
offlinePlayer.getUniqueId(),
|
||||||
|
Optional.ofNullable(reason).orElse("")
|
||||||
public void requestReports(Player issuer) {
|
|
||||||
URIBuilder uriBuilder = new URIBuilder(this.apiEndpoint);
|
|
||||||
uriBuilder.addParameter("secret", apiSecret);
|
|
||||||
uriBuilder.addParameter("uuid", issuer.getUniqueId().toString());
|
|
||||||
|
|
||||||
try(HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
HttpRequest httpRequest = HttpRequest.newBuilder()
|
|
||||||
.uri(uriBuilder.build())
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.GET()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
HttpResponse<String> httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
|
|
||||||
this.printReports(issuer, httpResponse);
|
|
||||||
} catch (IOException | InterruptedException | URISyntaxException e) {
|
|
||||||
issuer.sendMessage(
|
|
||||||
Component.text("Internal server description: " + e.getMessage()).color(NamedTextColor.RED)
|
|
||||||
);
|
);
|
||||||
throw new RuntimeException(e);
|
this.createReport(issuer, request);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void issueReport(Player issuer, Request reportRequest) {
|
private void createReport(Player issuer, ReportRepository.ReportCreationInfo reportRequest) {
|
||||||
URIBuilder uriBuilder = new URIBuilder(this.apiEndpoint);
|
ReqResp<ReportRepository.ReportUrl> createdReport = this.queryRepository(ReportRepository.class)
|
||||||
uriBuilder.addParameter("secret", apiSecret);
|
.createReport(reportRequest);
|
||||||
|
|
||||||
try(HttpClient client = HttpClient.newHttpClient()) {
|
switch(createdReport.status()) {
|
||||||
HttpRequest httpRequest = HttpRequest.newBuilder()
|
|
||||||
.uri(uriBuilder.build())
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(new Gson().toJson(reportRequest)))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
HttpResponse<String> httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
|
|
||||||
this.printResultMessage(issuer, httpResponse);
|
|
||||||
} catch(IOException | InterruptedException | URISyntaxException e) {
|
|
||||||
issuer.sendMessage(
|
|
||||||
Component.text("Internal server description: " + e.getMessage()).color(NamedTextColor.RED)
|
|
||||||
);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printReports(Player issuer, HttpResponse<String> httpResponse) {
|
|
||||||
if(httpResponse.statusCode() != 200) {
|
|
||||||
|
|
||||||
Main.logger().warning("Failed to request Reports: " + httpResponse.statusCode());
|
|
||||||
issuer.sendMessage(
|
|
||||||
Component.text()
|
|
||||||
.append(Component.text("Interner Serverfehler beim abfragen der Reports.", NamedTextColor.RED))
|
|
||||||
.appendNewline()
|
|
||||||
.append(Component.text("Bitte melde dich bei einem Admin!", NamedTextColor.RED))
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ReportInfo> reports = new Gson().fromJson(httpResponse.body(), ReportsResponse.class).from_self;
|
|
||||||
reports.removeIf(reportInfo -> reportInfo.draft);
|
|
||||||
if(reports.isEmpty()) {
|
|
||||||
issuer.sendMessage(
|
|
||||||
Component.text()
|
|
||||||
.append(Component.text("Du hast noch niemanden reportet.", NamedTextColor.RED))
|
|
||||||
.appendNewline()
|
|
||||||
.append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY))
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
|
|
||||||
|
|
||||||
component.append(
|
|
||||||
Component.newline()
|
|
||||||
.append(Component.text("Von dir erstellte Reports: ", NamedTextColor.GOLD))
|
|
||||||
.appendNewline()
|
|
||||||
);
|
|
||||||
|
|
||||||
reports.forEach(reportInfo -> {
|
|
||||||
component.append(
|
|
||||||
Component.text()
|
|
||||||
.append(Component.text(" - ", NamedTextColor.WHITE))
|
|
||||||
.append(Component.text(reportInfo.reported.username, NamedTextColor.WHITE))
|
|
||||||
.append(Component.text(String.format(": %s", reportInfo.subject), NamedTextColor.GRAY))
|
|
||||||
.clickEvent(ClickEvent.openUrl(reportInfo.url))
|
|
||||||
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um den Report einzusehen.", NamedTextColor.GOLD)))
|
|
||||||
);
|
|
||||||
component.appendNewline();
|
|
||||||
});
|
|
||||||
|
|
||||||
issuer.sendMessage(component.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printResultMessage(Player issuer, HttpResponse<String> httpResponse) {
|
|
||||||
switch(httpResponse.statusCode()) {
|
|
||||||
case 201:
|
case 201:
|
||||||
ReportResponse createdReport = new Gson().fromJson(httpResponse.body(), ReportResponse.class);
|
|
||||||
issuer.sendMessage(
|
issuer.sendMessage(
|
||||||
Component.text()
|
Component.text()
|
||||||
.append(Component.text("\\/".repeat(20), NamedTextColor.DARK_GRAY))
|
.append(Component.text("\\/".repeat(20), NamedTextColor.DARK_GRAY))
|
||||||
.appendNewline()
|
.appendNewline()
|
||||||
.appendNewline()
|
.append(Component.text("⚠ Der Report muss über den folgenden Link fertiggestellt werden!", NamedTextColor.GOLD))
|
||||||
.append(Component.text("!!! Report hier fertigstellen !!!", NamedTextColor.GOLD))
|
|
||||||
.appendNewline()
|
.appendNewline()
|
||||||
.appendNewline()
|
.appendNewline()
|
||||||
.append(
|
.append(
|
||||||
Component
|
Component
|
||||||
.text(" > Hier klicken < ", NamedTextColor.GREEN)
|
.text(createdReport.data().url(), NamedTextColor.GRAY) // URL mit Weltkugel-Emoji
|
||||||
.hoverEvent(HoverEvent.showText(Component.text(createdReport.url).color(NamedTextColor.GREEN)))
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, createdReport.data().url()))
|
||||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, createdReport.url))
|
|
||||||
)
|
)
|
||||||
.appendNewline()
|
.appendNewline()
|
||||||
.appendNewline()
|
.appendNewline()
|
||||||
.append(
|
.append(Component.text("Ohne das Fertigstellen des Reports wird dieser nicht bearbeitet!", NamedTextColor.DARK_RED))
|
||||||
Component
|
|
||||||
.text(createdReport.url, NamedTextColor.GRAY)
|
|
||||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, createdReport.url))
|
|
||||||
)
|
|
||||||
.appendNewline()
|
|
||||||
.appendNewline()
|
.appendNewline()
|
||||||
.append(Component.text("/\\".repeat(20), NamedTextColor.DARK_GRAY))
|
.append(Component.text("/\\".repeat(20), NamedTextColor.DARK_GRAY))
|
||||||
);
|
);
|
||||||
@ -213,7 +94,7 @@ public class Report extends Appliance {
|
|||||||
|
|
||||||
case 401:
|
case 401:
|
||||||
default:
|
default:
|
||||||
Main.logger().warning("Failed to request Report: " + httpResponse.statusCode());
|
Main.logger().warning("Failed to request Report: " + createdReport.status());
|
||||||
issuer.sendMessage(
|
issuer.sendMessage(
|
||||||
Component.text()
|
Component.text()
|
||||||
.append(Component.text("Interner Serverfehler beim anlegen des Reports.", NamedTextColor.RED))
|
.append(Component.text("Interner Serverfehler beim anlegen des Reports.", NamedTextColor.RED))
|
||||||
@ -224,6 +105,61 @@ public class Report extends Appliance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void queryReports(Player issuer) {
|
||||||
|
ReqResp<ReportRepository.PlayerReports> userReports = this.queryRepository(ReportRepository.class)
|
||||||
|
.queryReports(issuer.getUniqueId());
|
||||||
|
|
||||||
|
if(userReports.status() != 200) {
|
||||||
|
Main.logger().warning("Failed to request Reports: " + userReports.status());
|
||||||
|
issuer.sendMessage(
|
||||||
|
Component.text()
|
||||||
|
.append(Component.text("Interner Serverfehler beim abfragen der Reports.", NamedTextColor.RED))
|
||||||
|
.appendNewline()
|
||||||
|
.append(Component.text("Bitte melde dich bei einem Admin!", NamedTextColor.RED))
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ReportRepository.PlayerReports.Report> reports = userReports
|
||||||
|
.data()
|
||||||
|
.from_self()
|
||||||
|
.stream()
|
||||||
|
.filter(report -> !report.draft())
|
||||||
|
.toList()
|
||||||
|
.reversed();
|
||||||
|
|
||||||
|
if(reports.isEmpty()) {
|
||||||
|
issuer.sendMessage(
|
||||||
|
Component.text()
|
||||||
|
.append(Component.text("Du hast noch niemanden reportet.", NamedTextColor.RED))
|
||||||
|
.appendNewline()
|
||||||
|
.append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY))
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text()
|
||||||
|
.append(Component.newline())
|
||||||
|
.append(Component.text("Von dir erstellte Reports: ", NamedTextColor.GOLD))
|
||||||
|
.appendNewline();
|
||||||
|
|
||||||
|
reports.forEach(report -> {
|
||||||
|
component
|
||||||
|
.append(Component.text(" - ", NamedTextColor.WHITE))
|
||||||
|
.append(
|
||||||
|
report.reported() != null
|
||||||
|
? Component.text(report.reported().username(), NamedTextColor.WHITE)
|
||||||
|
: Component.text("Unbekannt", NamedTextColor.YELLOW)
|
||||||
|
)
|
||||||
|
.append(Component.text(String.format(": %s", report.subject()), NamedTextColor.GRAY))
|
||||||
|
.clickEvent(ClickEvent.openUrl(report.url()))
|
||||||
|
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um den Report einzusehen.", NamedTextColor.GOLD)));
|
||||||
|
component.appendNewline();
|
||||||
|
});
|
||||||
|
|
||||||
|
issuer.sendMessage(component.build());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
protected List<ApplianceCommand<?>> commands() {
|
protected List<ApplianceCommand<?>> commands() {
|
||||||
|
@ -14,6 +14,6 @@ public class ReportsCommand extends ApplianceCommand.PlayerChecked<Report> {
|
|||||||
@Override
|
@Override
|
||||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
sender.sendMessage(ComponentUtil.pleaseWait());
|
sender.sendMessage(ComponentUtil.pleaseWait());
|
||||||
getAppliance().requestReports(getPlayer());
|
getAppliance().queryReports(getPlayer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user