Browse Source

report hype v1

master
gsd 10 months ago
parent
commit
982628c028
  1. 36
      src/main/java/app/controllers/server/ServerReportController.java
  2. 5
      src/main/java/app/controllers/user/ProfileController.java
  3. 2
      src/main/java/app/entities/PlayerProfile.java
  4. 134
      src/main/java/app/entities/report/Report.java
  5. 25
      src/main/java/app/entities/report/ReportCount.java
  6. 19
      src/main/java/app/entities/report/ReportPermition.java
  7. 5
      src/main/java/app/entities/report/ReportType.java
  8. 12
      src/main/java/app/entities/server/request/PlayerOnServer.java
  9. 31
      src/main/java/app/services/ProfileService.java
  10. 95
      src/main/java/app/services/ReportService.java
  11. 6
      src/main/java/app/services/StatsService.java
  12. 189
      src/main/java/app/services/db/ReportService.java
  13. 4
      src/main/java/app/updates/SocialUpdater.java

36
src/main/java/app/controllers/server/ServerReportController.java

@ -0,0 +1,36 @@
package app.controllers.server;
import app.annotations.enums.AuthMethod;
import app.annotations.interfaces.CheckWebAccess;
import app.entities.report.ReportType;
import app.services.ProfileService;
import app.services.db.ReportService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("api/external/report")
public class ServerReportController {
private ReportService reportService;
private ProfileService profileService;
@Autowired
public ServerReportController(ReportService reportService,
ProfileService profileService) {
this.reportService = reportService;
this.profileService = profileService;
}
@PostMapping(value = "/{author_steam64}/{reported_steam64}")
@CheckWebAccess(auth_method = AuthMethod.SECRET_KEY)
public ResponseEntity sendReport(HttpServletRequest request,
@PathVariable(required = true) String author_steam64,
@PathVariable(required = false) String reported_steam64,
@RequestBody String text) {
return ResponseEntity.status(HttpStatus.OK).body(
reportService.createReport(ReportType.IN_GAME, profileService.GetProfile(author_steam64, "permition,steam_data"), profileService.GetProfile(reported_steam64, "permition"), text));
}
}

5
src/main/java/app/controllers/user/ProfileController.java

@ -1,14 +1,14 @@
package app.controllers.user;
import app.annotations.enums.AuthMethod;
import app.annotations.enums.CollectStages;
import app.annotations.interfaces.*;
import app.controllers.other.ExternalVIPController;
import app.entities.SocialAuth;
import app.entities.VipGiveMethod;
import app.entities.VipPrice;
import app.entities.report.ReportType;
import app.services.ProfileService;
import app.services.ReportService;
import app.services.db.ReportService;
import app.services.db.FreeVIPService;
import app.services.db.VIPService;
import app.utils.SteamIDConverter;
@ -96,6 +96,7 @@ public class ProfileController {
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).build();
return new ResponseEntity(reportService.createReport(
ReportType.WEBSITE,
profileService.GetProfile(steam64, "permition,steam_data"),
profileService.GetProfile(reported_steam64, "permition"),
text

2
src/main/java/app/entities/PlayerProfile.java

@ -5,6 +5,7 @@ import app.entities.db.DonateStat;
import app.entities.db.Killfeed;
import app.entities.db.Permition;
import app.entities.other.SteamID;
import app.entities.report.ReportCount;
import app.entities.server.PlayOn;
import app.entities.steam.SteamData;
import com.fasterxml.jackson.annotation.JsonIgnore;
@ -30,4 +31,5 @@ public class PlayerProfile {
List<Ban> ban_list;
Killfeed killfeed;
Long messages;
ReportCount reports;
}

134
src/main/java/app/entities/report/Report.java

@ -0,0 +1,134 @@
package app.entities.report;
import app.entities.other.SteamID;
import app.utils.SteamIDConverter;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Report {
private final Integer id;
//author
private final String a_nickname;
private final String a_steam2;
private final Integer a_permition;
private final Integer a_kills;
private final Integer a_deads;
private final Integer a_seconds;
//reported
private final String r_nickname;
private final String r_steam2;
private final Integer r_permition;
private final Integer r_kills;
private final Integer r_deads;
private final Integer r_seconds;
//based
private final String reasons;
private final long utime;
private final String srv;
private final Integer online;
private final Integer type;
public Report(ResultSet rs) throws SQLException {
id = rs.getInt("id");
//
a_nickname = rs.getString("a_nickname");
a_steam2 = rs.getString("a_steam2");
a_permition = rs.getInt("a_permition");
a_kills = rs.getInt("a_kills");
a_deads = rs.getInt("a_deads");
a_seconds = rs.getInt("a_seconds");
//
r_nickname = rs.getString("r_nickname");
r_steam2 = rs.getString("r_steam2");
r_permition = rs.getInt("r_permition");
r_kills = rs.getInt("r_kills");
r_deads = rs.getInt("r_deads");
r_seconds = rs.getInt("r_seconds");
//based
reasons = rs.getString("reasons");
utime = rs.getLong("utime");
srv = rs.getString("srv");
online = rs.getInt("online");
type = rs.getInt("type");
}
public Integer getId() {
return id;
}
public String getA_nickname() {
return a_nickname;
}
public ReportPermition getA_permition() {
return ReportPermition.values()[a_permition];
}
public Integer getA_kills() {
return a_kills;
}
public Integer getA_deads() {
return a_deads;
}
public Integer getA_seconds() {
return a_seconds;
}
public String getR_nickname() {
return r_nickname;
}
public ReportPermition getR_permition() {
return ReportPermition.values()[r_permition];
}
public Integer getR_kills() {
return r_kills;
}
public Integer getR_deads() {
return r_deads;
}
public Integer getR_seconds() {
return r_seconds;
}
public String getReasons() {
return reasons;
}
public long getUtime() {
return utime;
}
public String getSrv() {
return srv;
}
public Integer getOnline() {
return online;
}
public ReportType getType() {
return ReportType.values()[type];
}
public SteamID getA_steam() {
return SteamIDConverter.getSteamID(a_steam2);
}
public SteamID getR_steam() {
return SteamIDConverter.getSteamID(r_steam2);
}
public boolean selfReport() {
return r_nickname == null && r_steam2 == null;
}
}

25
src/main/java/app/entities/report/ReportCount.java

@ -0,0 +1,25 @@
package app.entities.report;
public class ReportCount {
private final long created;
private final long accepted;
private final long selfcount;
public ReportCount(long created, long accepted, long selfcount) {
this.created = created;
this.accepted = accepted;
this.selfcount = selfcount;
}
public long getCreated() {
return created;
}
public long getAccepted() {
return accepted;
}
public long getSelfcount() {
return selfcount;
}
}

19
src/main/java/app/entities/report/ReportPermition.java

@ -0,0 +1,19 @@
package app.entities.report;
public enum ReportPermition {
DONT_HAVE,
ROOT,
ADMIN,
MODERATOR,
FREE,
VIP,
UNKNOWN;
public String getPrefixToDiscord() {
if (this.equals(DONT_HAVE)) return "";
if (this.equals(UNKNOWN)) return "";
return new StringBuilder(" | ")
.append(toString().toUpperCase())
.toString();
}
}

5
src/main/java/app/entities/report/ReportType.java

@ -0,0 +1,5 @@
package app.entities.report;
public enum ReportType {
IN_GAME, WEBSITE
}

12
src/main/java/app/entities/server/request/PlayerOnServer.java

@ -21,4 +21,16 @@ public class PlayerOnServer extends RCONPlayer {
}
public int clz = 0;
public int team = 0;
public int deads = 0;
public int getDuration_seconds() {
try {
if (getDuration() == null || getDuration().isEmpty()) return 0;
String[] splitted = getDuration().split(":");
if (splitted.length == 1) return Integer.parseInt(splitted[0]);
if (splitted.length == 2) return Integer.parseInt(splitted[0]) * 60 + Integer.parseInt(splitted[1]);
if (splitted.length == 3) return Integer.parseInt(splitted[0]) * 60 * 60 + Integer.parseInt(splitted[1]) * 60 + Integer.parseInt(splitted[2]);
if (splitted.length == 4) return Integer.parseInt(splitted[0]) * 60 * 60 * 24 + Integer.parseInt(splitted[1]) * 60 * 60 + Integer.parseInt(splitted[2]) * 60 + Integer.parseInt(splitted[3]);
else return -1;
} catch (Exception e) { return 0; }
}
}

31
src/main/java/app/services/ProfileService.java

@ -5,6 +5,7 @@ import app.entities.Stats;
import app.entities.db.Ban;
import app.entities.db.Killfeed;
import app.entities.other.SteamID;
import app.entities.report.ReportCount;
import app.exceptions.BaseWebException;
import app.services.db.*;
import app.services.steam.SteamWebApi;
@ -37,6 +38,7 @@ public class ProfileService {
DonateService donateService;
MessageService messageService;
KillfeedService killfeedService;
ReportService reportService;
@Autowired
public ProfileService(SteamWebApi steamWebApi,
@ -48,7 +50,8 @@ public class ProfileService {
DetectService detectService,
DonateService donateService,
MessageService messageService,
KillfeedService killfeedService) {
KillfeedService killfeedService,
ReportService reportService) {
this.steamWebApi = steamWebApi;
this.usertimeService = usertimeService;
this.permitionService = permitionService;
@ -59,6 +62,7 @@ public class ProfileService {
this.donateService = donateService;
this.messageService = messageService;
this.killfeedService = killfeedService;
this.reportService = reportService;
}
public PlayerProfile GetProfile(SteamID steamID, List<String> requests) {
@ -70,13 +74,17 @@ public class ProfileService {
}
public PlayerProfile GetProfile(String steam64, List<String> requests) {
SteamID steamID = SteamIDConverter.getSteamID(steam64);
if (null == steamID) return null;
final int requests_pool = 4;
ExecutorService executor = Executors.newFixedThreadPool(requests_pool);
Set<Callable<Supplier>> callables = new HashSet<>(requests_pool);
PlayerProfile profile = new PlayerProfile();
profile.setResponse_time(new HashMap<>());
SteamID steamID = SteamIDConverter.getSteamID(steam64);
profile.setSteamids(steamID);
@ -223,6 +231,23 @@ public class ProfileService {
});
}
if(requests.contains("reports")){
callables.add(() -> {
try {
long start_time = Instant.now().toEpochMilli();
profile.setReports(new ReportCount(
reportService.getCreatedReportsCount(steamID),
reportService.getReportsCount(steamID),
reportService.getCreatedSelfReportsCount(steamID)
));
long end_time = Instant.now().toEpochMilli() - start_time;
} catch (Exception err) {
err.printStackTrace();
}
return null;
});
}
//if(requests.contains("messages")){
callables.add(() -> {
try {
@ -250,7 +275,7 @@ public class ProfileService {
}
public PlayerProfile GetProfile(String steam64) {
return GetProfile(steam64, List.of("steam_data,lastplay,usertime,permition,ban,attached_discord,donates,ban_list,killfeed".split(",")));
return GetProfile(steam64, List.of("steam_data,lastplay,usertime,permition,ban,attached_discord,donates,ban_list,killfeed,reports".split(",")));
}
public PlayerProfile GetProfile(String steam64, String requests) {

95
src/main/java/app/services/ReportService.java

@ -1,95 +0,0 @@
package app.services;
import app.entities.PlayerProfile;
import app.entities.Stats;
import app.entities.db.Permition;
import app.entities.server.players.RCONPlayer;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ReportService {
@Value("${backend.report.webhook_url}")
String webhook_url;
@Value("${backend.report.kd}")
long kd;
HashMap<Long, Long> userKD;
Stats stats;
StatsService statsService;
String tf2_icon = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/440/e3f595a92552da3d664ad00277fad2107345f743.jpg";
RestTemplate restTemplate;
@Autowired
public ReportService(Stats stats,
StatsService statsService) {
this.stats = stats;
this.statsService = statsService;
userKD = new HashMap<>();
restTemplate = new RestTemplate();
}
public long createReport(PlayerProfile current_user, PlayerProfile reported_user, String text) {
if (reported_user.getPlay_on() == null) return 0L;
if (userKD.containsKey(current_user.getSteamids().steam64) &&
(Instant.now().getEpochSecond() - userKD.get(current_user.getSteamids().steam64)) < kd) {
return (Instant.now().getEpochSecond() - userKD.get(current_user.getSteamids().steam64)) - kd;
}
String author = "%s | %s%s | WEBSITE".formatted(
current_user.getSteam_data().getNickname(),
current_user.getSteamids().steam2,
Permition2Prefix(current_user.getPermition())
);
RCONPlayer reported_player = statsService.searchPlayer(reported_user.getPlay_on());
String reported = "%s | %s%s | %d/? %s".formatted(
reported_player.getName(),
reported_user.getSteamids().steam2,
Permition2Prefix(reported_user.getPermition()),
reported_player.getScore(),
reported_player.getDuration()
);
//Create Embed
HashMap<String, Object> embed = new HashMap<>();
embed.put("author", Map.of("name", author, "url", current_user.getSteamids().community_url));
embed.put("title", reported);
embed.put("url", reported_user.getSteamids().community_url);
embed.put("description", text);
embed.put("color", 1488);
embed.put("footer", Map.of(
"text", "%s • %d/%d".formatted(
stats.getServers().get(reported_user.getPlay_on().getServer_id()).getName(),
stats.getServers().get(reported_user.getPlay_on().getServer_id()).getPlayer_count(),
stats.getServers().get(reported_user.getPlay_on().getServer_id()).getMax_players()),
"icon_url", tf2_icon
));
HashMap<String, Object> payload = new HashMap<>();
payload.put("embeds", List.of(embed));
restTemplate.postForEntity(webhook_url, (Object) payload, String.class);
userKD.merge(current_user.getSteamids().steam64, Instant.now().getEpochSecond(), (x,y) -> y);
return userKD.get(current_user.getSteamids().steam64);
}
public String Permition2Prefix(Permition permition) {
if (permition == null) return "";
if (permition.getFlags().contains("z")) return " | [ROOT]";
if (permition.getFlags().contains("b")) return " | [ADMIN]";
if (permition.getFlags().contains("d")) return " | [MODERATOR]";
if (permition.getFlags().contains("t")) return " | [FREE]";
if (permition.getFlags().contains("a")) return " | [VIP]";
return "";
}
}

6
src/main/java/app/services/StatsService.java

@ -1,5 +1,6 @@
package app.services;
import app.entities.PlayerProfile;
import app.entities.Stats;
import app.entities.other.SteamID;
import app.entities.server.PlayOn;
@ -46,7 +47,12 @@ public class StatsService {
return null;
}
public RCONPlayer searchPlayer(PlayerProfile playerProfile) {
return searchPlayer(playerProfile.getPlay_on());
}
public RCONPlayer searchPlayer(PlayOn playOn) {
if (null == playOn) return null;
return (RCONPlayer) stats.getServers().get(playOn.getServer_id()).getPlayers().stream()
.filter(player -> player.getId() != 0 && player.getId() == playOn.getPlayer_id()).findFirst().orElse(null);
}

189
src/main/java/app/services/db/ReportService.java

@ -0,0 +1,189 @@
package app.services.db;
import app.entities.PlayerProfile;
import app.entities.Stats;
import app.entities.db.Permition;
import app.entities.other.SteamID;
import app.entities.report.Report;
import app.entities.report.ReportPermition;
import app.entities.report.ReportType;
import app.entities.server.players.RCONPlayer;
import app.entities.server.request.PlayerOnServer;
import app.services.ProfileService;
import app.services.StatsService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ReportService {
@Value("${backend.report.webhook_url}")
String webhook_url;
@Value("${backend.report.kd}")
long kd;
HashMap<Long, Long> userKD;
Stats stats;
StatsService statsService;
String tf2_icon = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/440/e3f595a92552da3d664ad00277fad2107345f743.jpg";
RestTemplate restTemplate;
@Autowired
@Qualifier("jt_rw")
private JdbcTemplate jdbcTemplate_rw;
@Autowired
@Qualifier("jt_ro")
private JdbcTemplate jdbcTemplate_ro;
@Autowired
public ReportService(Stats stats,
StatsService statsService) {
this.stats = stats;
this.statsService = statsService;
userKD = new HashMap<>();
restTemplate = new RestTemplate();
}
private void addReport(PlayerProfile current_user, PlayerProfile reported_user, String text, ReportType reportType) {
PlayerOnServer current_player = (PlayerOnServer) statsService.searchPlayer(current_user);
PlayerOnServer reported_player = (PlayerOnServer) statsService.searchPlayer(reported_user);
jdbcTemplate_rw.update("INSERT INTO user_reports (a_nickname, a_steam2, a_permition, a_kills, a_deads, a_seconds, " +
"r_nickname, r_steam2, r_permition, r_kills, r_deads, r_seconds, " +
"reasons, utime, srv, online, type) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
current_player == null ? current_user.getSteam_data().getNickname() : current_player.getName(), current_user.getSteamids().steam2, Permition2Prefix(current_user.getPermition()).ordinal(),
current_player == null?null:current_player.getScore(), current_player == null?null:current_player.getDeads(),current_player == null?null:current_player.getDuration_seconds(),
reported_player==null?null:reported_player.getName(),
reported_player==null?null:reported_user.getSteamids().steam2,
reported_player==null?null:Permition2Prefix(reported_user.getPermition()).ordinal(),
reported_player==null?null:reported_player.getScore(),
reported_player==null?null:reported_player.getDeads(),
reported_player==null?null:reported_player.getDuration_seconds(),
text, Instant.now().getEpochSecond(), reported_user.getPlay_on().getServer_id(),
stats.getServers().get(reported_user.getPlay_on().getServer_id()).getPlayer_count(),
reportType.ordinal());
}
public long getCreatedReportsCount(SteamID steamID) {
return jdbcTemplate_ro.query("SELECT COUNT(*) as c FROM user_reports WHERE a_steam2 LIKE ?", new Object[]{steamID.steam2}, (rs,n) -> rs.getLong("c")).get(0);
}
public long getReportsCount(SteamID steamID) {
return jdbcTemplate_ro.query("SELECT COUNT(*) as c FROM user_reports WHERE r_steam2 LIKE ?", new Object[]{steamID.steam2}, (rs,n) -> rs.getLong("c")).get(0);
}
public long getCreatedSelfReportsCount(SteamID steamID) {
return jdbcTemplate_ro.query("SELECT COUNT(*) as c FROM user_reports WHERE a_steam2 LIKE ? AND r_steam2 IS NULL", new Object[]{steamID.steam2}, (rs,n) -> rs.getLong("c")).get(0);
}
public List<Report> getCreatedReports(SteamID steamID) {
return jdbcTemplate_ro.query("SELECT * FROM user_reports WHERE a_steam2 LIKE ?", new Object[]{steamID.steam2}, (rs,n) -> new Report(rs));
}
public List<Report> getReports(SteamID steamID) {
return jdbcTemplate_ro.query("SELECT * FROM user_reports WHERE r_steam2 LIKE ?", new Object[]{steamID.steam2}, (rs,n) -> new Report(rs));
}
public long createReport(ReportType reportType, PlayerProfile current_user, PlayerProfile reported_user, String text) {
switch (reportType) {
case WEBSITE -> {
if (reported_user.getPlay_on() == null) return 0L;
if (userKD.containsKey(current_user.getSteamids().steam64) &&
(Instant.now().getEpochSecond() - userKD.get(current_user.getSteamids().steam64)) < kd) {
return (Instant.now().getEpochSecond() - userKD.get(current_user.getSteamids().steam64)) - kd;
}
addReport(current_user, reported_user, text, reportType);
long res = createReportToDiscord(current_user, reported_user, text);
if (res == 0) return 0;
userKD.merge(current_user.getSteamids().steam64, res, (x,y) -> y);
return res;
}
case IN_GAME -> {
addReport(current_user, reported_user, text, reportType);
return createReportToDiscord(current_user, reported_user, text);
}
default -> {
return -1;
}
}
}
private long createReportToDiscord(PlayerProfile current_user, PlayerProfile reported_user, String text) {
PlayerOnServer current_player = (PlayerOnServer) statsService.searchPlayer(current_user);
String author = "%s | %s%s | %s".formatted(
current_player == null ? current_user.getSteam_data().getNickname() : current_player.getName(),
current_user.getSteamids().steam2,
Permition2PrefixToDiscord(current_user.getPermition()),
current_player == null?"WEBSITE":"%d/%d %s".formatted(
current_player.getScore(), current_player.getDeads(), current_player.getDuration())
);
String reported = "";
if (reported_user != null) {
PlayerOnServer reported_player = (PlayerOnServer) statsService.searchPlayer(reported_user);
if (reported_player == null) return 0L;
reported = "%s | %s%s | %d/%d %s".formatted(
reported_player.getName(),
reported_user.getSteamids().steam2,
Permition2PrefixToDiscord(reported_user.getPermition()),
reported_player.getScore(),
reported_player.getDeads(),
reported_player.getDuration()
);
}
//Create Embed
HashMap<String, Object> embed = new HashMap<>();
embed.put("author", Map.of("name", author, "url", current_user.getSteamids().community_url));
if (reported_user != null)
embed.put("title", reported);
embed.put("url", reported_user.getSteamids().community_url);
embed.put("description", text);
embed.put("color", 16581375); // 255 * 255 * 255 | black
embed.put("footer", Map.of(
"text", "%s • %d/%d ".formatted(
stats.getServers().get(reported_user.getPlay_on().getServer_id()).getName(),
stats.getServers().get(reported_user.getPlay_on().getServer_id()).getPlayer_count(),
stats.getServers().get(reported_user.getPlay_on().getServer_id()).getMax_players()),
"icon_url", tf2_icon
));
embed.put("timestamp", Instant.now().toString());
HashMap<String, Object> payload = new HashMap<>();
payload.put("embeds", List.of(embed));
restTemplate.postForEntity(webhook_url, (Object) payload, String.class);
return Instant.now().getEpochSecond();
}
private String Permition2PrefixToDiscord(Permition permition) {
return Permition2Prefix(permition).getPrefixToDiscord();
}
private ReportPermition Permition2Prefix(Permition permition) {
if (permition == null) return ReportPermition.DONT_HAVE;
if (permition.getFlags().contains("z")) return ReportPermition.ROOT;
if (permition.getFlags().contains("b")) return ReportPermition.ADMIN;
if (permition.getFlags().contains("d")) return ReportPermition.MODERATOR;
if (permition.getFlags().contains("t")) return ReportPermition.FREE;
if (permition.getFlags().contains("a")) return ReportPermition.VIP;
return ReportPermition.UNKNOWN;
}
}

4
src/main/java/app/updates/SocialUpdater.java

@ -58,6 +58,7 @@ public class SocialUpdater extends BaseUpdater{
public boolean UpdateVKCount() {
logger.info("Request vk group count users");
try {
int count = 0;
String response = restTemplate.getForEntity(vk_url, String.class).getBody();
int k_start = response.indexOf("<h3 class=\"slim_header\">") + "<h3 class=\"slim_header\">".length();
@ -68,6 +69,9 @@ public class SocialUpdater extends BaseUpdater{
count += Integer.valueOf(response.substring(s_start, s_end));
stats.setVk_users(count);
stats.getUpdates().merge("vk_count", Instant.now().getEpochSecond(), (x, y) -> y);
} catch (NumberFormatException nfe) {
logger.error("Cannot update vk group count");
}
return true;
}
}

Loading…
Cancel
Save