Implementing /news add and delete commands.
This commit is contained in:
parent
82ba7bf30f
commit
2ec8371f98
@ -28,9 +28,11 @@ public class HubRepository
|
|||||||
|
|
||||||
private static String CREATE_NEWS_TABLE = "CREATE TABLE IF NOT EXISTS newsList (id INT NOT NULL AUTO_INCREMENT, newsString VARCHAR(256), newsPosition INT, PRIMARY KEY (id));";
|
private static String CREATE_NEWS_TABLE = "CREATE TABLE IF NOT EXISTS newsList (id INT NOT NULL AUTO_INCREMENT, newsString VARCHAR(256), newsPosition INT, PRIMARY KEY (id));";
|
||||||
private static String RETRIEVE_NEWS_ENTRIES = "SELECT newsString, newsPosition FROM newsList;";
|
private static String RETRIEVE_NEWS_ENTRIES = "SELECT newsString, newsPosition FROM newsList;";
|
||||||
|
private static String RETRIEVE_MAX_NEWS_POSITION = "SELECT MAX(newsPosition) AS newsPosition FROM newsList;";
|
||||||
private static String ADD_NEWS_ENTRY = "INSERT INTO newsList (newsString, newsPosition) VALUES(?,?);";
|
private static String ADD_NEWS_ENTRY = "INSERT INTO newsList (newsString, newsPosition) VALUES(?,?);";
|
||||||
private static String SET_NEWS_ENTRY = "UPDATE newsList SET newsString = ? WHERE newsPosition = ?;";
|
private static String SET_NEWS_ENTRY = "UPDATE newsList SET newsString = ? WHERE newsPosition = ?;";
|
||||||
private static String DELETE_NEWS_ENTRY = "DELETE FROM newsList WHERE newsPosition = ?;";
|
private static String DELETE_NEWS_ENTRY = "DELETE FROM newsList WHERE newsPosition = ?;";
|
||||||
|
private static String RECALC_NEWS_POSITIONS = "UPDATE newsList SET newsPosition = newsPosition - 1 WHERE newsPosition > ?;";
|
||||||
|
|
||||||
private Connection _connection = null;
|
private Connection _connection = null;
|
||||||
|
|
||||||
@ -171,4 +173,146 @@ public class HubRepository
|
|||||||
|
|
||||||
return result != 0;
|
return result != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int retrieveMaxNewsPosition()
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
ResultSet resultSet = null;
|
||||||
|
PreparedStatement preparedStatement = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
synchronized (_connectionLock)
|
||||||
|
{
|
||||||
|
if (_connection.isClosed())
|
||||||
|
{
|
||||||
|
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
|
||||||
|
}
|
||||||
|
|
||||||
|
preparedStatement = _connection.prepareStatement(RETRIEVE_MAX_NEWS_POSITION);
|
||||||
|
resultSet = preparedStatement.executeQuery();
|
||||||
|
|
||||||
|
while (resultSet.next())
|
||||||
|
{
|
||||||
|
result = Integer.parseInt(resultSet.getString(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (preparedStatement != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
preparedStatement.close();
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addNewsEntry(String newsEntry)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
int maxPos = retrieveMaxNewsPosition();
|
||||||
|
PreparedStatement preparedStatement = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
synchronized (_connectionLock)
|
||||||
|
{
|
||||||
|
if (_connection.isClosed())
|
||||||
|
{
|
||||||
|
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
|
||||||
|
}
|
||||||
|
|
||||||
|
preparedStatement = _connection.prepareStatement(ADD_NEWS_ENTRY);
|
||||||
|
preparedStatement.setString(1, newsEntry);
|
||||||
|
preparedStatement.setInt(2, maxPos + 1);
|
||||||
|
|
||||||
|
result = preparedStatement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (preparedStatement != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
preparedStatement.close();
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean deleteNewsEntry(int newsPosition)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
int maxPos = retrieveMaxNewsPosition();
|
||||||
|
PreparedStatement preparedStatement = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
synchronized (_connectionLock)
|
||||||
|
{
|
||||||
|
if (_connection.isClosed())
|
||||||
|
{
|
||||||
|
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
|
||||||
|
}
|
||||||
|
|
||||||
|
preparedStatement = _connection.prepareStatement(DELETE_NEWS_ENTRY);
|
||||||
|
preparedStatement.setInt(1, newsPosition);
|
||||||
|
result = preparedStatement.executeUpdate();
|
||||||
|
|
||||||
|
if (result != 0 && maxPos != newsPosition)
|
||||||
|
{
|
||||||
|
preparedStatement.close();
|
||||||
|
|
||||||
|
preparedStatement = _connection.prepareStatement(RECALC_NEWS_POSITIONS);
|
||||||
|
preparedStatement.setInt(1, newsPosition);
|
||||||
|
|
||||||
|
result = preparedStatement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (preparedStatement != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
preparedStatement.close();
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result != 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import mineplex.hub.HubManager;
|
|||||||
import mineplex.hub.modules.NewsManager;
|
import mineplex.hub.modules.NewsManager;
|
||||||
|
|
||||||
public class NewsCommand extends CommandBase<HubManager>
|
public class NewsCommand extends CommandBase<HubManager>
|
||||||
{
|
{
|
||||||
public NewsCommand(HubManager plugin)
|
public NewsCommand(HubManager plugin)
|
||||||
{
|
{
|
||||||
super(plugin, Rank.ADMIN, "news");
|
super(plugin, Rank.ADMIN, "news");
|
||||||
@ -24,7 +24,7 @@ public class NewsCommand extends CommandBase<HubManager>
|
|||||||
@Override
|
@Override
|
||||||
public void Execute(final Player caller, final String[] args)
|
public void Execute(final Player caller, final String[] args)
|
||||||
{
|
{
|
||||||
NewsManager newsMang = Plugin.GetNewsManager();
|
final NewsManager newsMang = Plugin.GetNewsManager();
|
||||||
|
|
||||||
if (args == null || args.length == 0)
|
if (args == null || args.length == 0)
|
||||||
{
|
{
|
||||||
@ -35,45 +35,110 @@ public class NewsCommand extends CommandBase<HubManager>
|
|||||||
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cWhite + "news add <newsEntry>" + C.cGray + " - Adds specified news entry string to database at end of table."));
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cWhite + "news add <newsEntry>" + C.cGray + " - Adds specified news entry string to database at end of table."));
|
||||||
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cWhite + "news delete #" + C.cGray + " - Removes specified (numbered) news entry string from database."));
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cWhite + "news delete #" + C.cGray + " - Removes specified (numbered) news entry string from database."));
|
||||||
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cWhite + "news set # <newsEntry>" + C.cGray + " - Updates specified (numbered) news entry string in database."));
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cWhite + "news set # <newsEntry>" + C.cGray + " - Updates specified (numbered) news entry string in database."));
|
||||||
return;
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cWhite + "*Please Note: " + C.cGray + "Updates to server news entries from the database are on a 4 minute refresh cycle!"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (args.length == 1 && args[0].equalsIgnoreCase("list"))
|
else if (args.length == 1 && args[0].equalsIgnoreCase("list"))
|
||||||
{
|
{
|
||||||
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cGray + "Current server news messages will be listed here..."));
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cGray + "Current server news messages:"));
|
||||||
|
|
||||||
newsMang.RetriveNewsEntries(new Callback<HashMap<String, String>>()
|
newsMang.RetriveNewsEntries(new Callback<HashMap<String, String>>()
|
||||||
{
|
{
|
||||||
public void run(HashMap<String, String> newsEntries)
|
public void run(final HashMap<String, String> newsEntries)
|
||||||
{
|
{
|
||||||
for (Iterator<String> iterator = newsEntries.keySet().iterator(); iterator.hasNext();)
|
// Order newsEntries set or its output by newsPosition, not hash order...
|
||||||
|
newsMang.RetrieveMaxNewsPosition(new Callback<Integer>()
|
||||||
{
|
{
|
||||||
String newsPosition = iterator.next();
|
public void run(Integer maxPosition)
|
||||||
String newsEntry = newsEntries.get(newsPosition);
|
{
|
||||||
|
String[] newsStrings = new String[maxPosition];
|
||||||
|
for (Iterator<String> iterator = newsEntries.keySet().iterator(); iterator.hasNext();)
|
||||||
|
{
|
||||||
|
String newsPosition = iterator.next();
|
||||||
|
newsStrings[Integer.parseInt(newsPosition) - 1] = newsEntries.get(newsPosition);
|
||||||
|
//iterator.remove();
|
||||||
|
}
|
||||||
|
|
||||||
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cGold + "News " + newsPosition + C.cGray + " : " + newsEntry));
|
for (int i = 0; i < newsStrings.length; i++)
|
||||||
//iterator.remove();
|
{
|
||||||
}
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cGold + "News " + (i + 1) + C.cGray + " : " + newsStrings[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (args.length == 2)
|
else if (args.length >= 2 && args.length <= 128)
|
||||||
{
|
{
|
||||||
if (args[0].equalsIgnoreCase("add"))
|
if (args[0].equalsIgnoreCase("delete") && args.length == 2)
|
||||||
{
|
{
|
||||||
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cGray + "This is where future messages like, " + C.cGold + "'" + args[1] + "'" + C.cGray + " will be added to server news!"));
|
final int newsPosition;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newsPosition = Integer.parseInt(args[1]);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cRed + "The specified news position is invalid!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newsMang.DeleteNewsEntry(newsPosition, new Callback<Boolean>()
|
||||||
|
{
|
||||||
|
public void run(Boolean success)
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cGray + "The news entry at position " + C.cGold + newsPosition + C.cGray + " has been deleted!"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cRed + "There was an error deleting the news entry; likely the specified news position was invalid!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (args[0].equalsIgnoreCase("delete"))
|
else if (args[0].equalsIgnoreCase("add"))
|
||||||
{
|
{
|
||||||
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cGray + "This is where future messages at positions like, " + C.cGold + "'News Position: " + C.cGold + args[1] + "'" + C.cGray + " will be removed from server news!"));
|
String newsEntry = "";
|
||||||
|
for (int i = 1; i < args.length; i++)
|
||||||
|
{
|
||||||
|
newsEntry += args[i] + " ";
|
||||||
|
}
|
||||||
|
newsEntry = newsEntry.substring(0, newsEntry.length() - 1);
|
||||||
|
|
||||||
|
// Check for 256 character length for MySQL!
|
||||||
|
if (newsEntry.length() > 256)
|
||||||
|
{
|
||||||
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cRed + "The specified news entry is too long [> 256 characters]!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newsMang.AddNewsEntry(newsEntry, new Callback<Boolean>()
|
||||||
|
{
|
||||||
|
public void run(Boolean success)
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
String newsEntry = "";
|
||||||
|
for (int i = 1; i < args.length; i++)
|
||||||
|
{
|
||||||
|
newsEntry += args[i] + " ";
|
||||||
|
}
|
||||||
|
newsEntry = newsEntry.substring(0, newsEntry.length() - 1);
|
||||||
|
|
||||||
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cGray + "The news entry: " + C.cGold + newsEntry + C.cGray + " has been added to the database!"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cRed + "There was an error adding the news entry to the database!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
else if (args[0].equalsIgnoreCase("set"))
|
||||||
else if (args.length >= 3 && args.length <= 128)
|
|
||||||
{
|
|
||||||
if (args[0].equalsIgnoreCase("set"))
|
|
||||||
{
|
{
|
||||||
final int newsPosition;
|
final int newsPosition;
|
||||||
String newsEntry = "";
|
String newsEntry = "";
|
||||||
@ -83,7 +148,12 @@ public class NewsCommand extends CommandBase<HubManager>
|
|||||||
}
|
}
|
||||||
newsEntry = newsEntry.substring(0, newsEntry.length() - 1);
|
newsEntry = newsEntry.substring(0, newsEntry.length() - 1);
|
||||||
|
|
||||||
// TODO: Check for 256 character length for MySQL!
|
// Check for 256 character length for MySQL!
|
||||||
|
if (newsEntry.length() > 256)
|
||||||
|
{
|
||||||
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cRed + "The specified news entry is too long [> 256 characters]!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -117,6 +187,8 @@ public class NewsCommand extends CommandBase<HubManager>
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
UtilPlayer.message(caller, F.main(Plugin.GetName(), C.cRed + "Your arguments are inappropriate for this command!"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package mineplex.hub.modules;
|
package mineplex.hub.modules;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
@ -10,7 +11,9 @@ import org.bukkit.event.EventHandler;
|
|||||||
import mineplex.core.MiniPlugin;
|
import mineplex.core.MiniPlugin;
|
||||||
import mineplex.core.common.util.C;
|
import mineplex.core.common.util.C;
|
||||||
import mineplex.core.common.util.Callback;
|
import mineplex.core.common.util.Callback;
|
||||||
|
import mineplex.core.common.util.F;
|
||||||
import mineplex.core.common.util.UtilDisplay;
|
import mineplex.core.common.util.UtilDisplay;
|
||||||
|
import mineplex.core.common.util.UtilPlayer;
|
||||||
import mineplex.core.common.util.UtilServer;
|
import mineplex.core.common.util.UtilServer;
|
||||||
import mineplex.core.common.util.UtilTime;
|
import mineplex.core.common.util.UtilTime;
|
||||||
import mineplex.core.updater.UpdateType;
|
import mineplex.core.updater.UpdateType;
|
||||||
@ -45,6 +48,33 @@ public class NewsManager extends MiniPlugin
|
|||||||
"Champions: " + C.cYellow + C.Bold + "Team Deathmatch Beta",
|
"Champions: " + C.cYellow + C.Bold + "Team Deathmatch Beta",
|
||||||
"Survival: " + C.cGreen + C.Bold + "UHC Alpha",
|
"Survival: " + C.cGreen + C.Bold + "UHC Alpha",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RefreshNews();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshNews()
|
||||||
|
{
|
||||||
|
RetriveNewsEntries(new Callback<HashMap<String, String>>()
|
||||||
|
{
|
||||||
|
public void run(final HashMap<String, String> newsEntries)
|
||||||
|
{
|
||||||
|
// Order newsEntries set or its output by newsPosition, not hash order...
|
||||||
|
RetrieveMaxNewsPosition(new Callback<Integer>()
|
||||||
|
{
|
||||||
|
public void run(Integer maxPosition)
|
||||||
|
{
|
||||||
|
String[] newsStrings = new String[maxPosition];
|
||||||
|
for (Iterator<String> iterator = newsEntries.keySet().iterator(); iterator.hasNext();)
|
||||||
|
{
|
||||||
|
String newsPosition = iterator.next();
|
||||||
|
newsStrings[Integer.parseInt(newsPosition) - 1] = newsEntries.get(newsPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
_news = newsStrings;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RetriveNewsEntries(final Callback<HashMap<String, String>> callback)
|
public void RetriveNewsEntries(final Callback<HashMap<String, String>> callback)
|
||||||
@ -91,6 +121,81 @@ public class NewsManager extends MiniPlugin
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddNewsEntry(final String newsEntry, final Callback<Boolean> callback)
|
||||||
|
{
|
||||||
|
if (callback == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(GetPlugin(), new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
final Boolean success = _repository.addNewsEntry(newsEntry);
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTask(GetPlugin(), new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
callback.run(success);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteNewsEntry(final int newsPosition, final Callback<Boolean> callback)
|
||||||
|
{
|
||||||
|
if (callback == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(GetPlugin(), new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
final Boolean success = _repository.deleteNewsEntry(newsPosition);
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTask(GetPlugin(), new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
callback.run(success);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RetrieveMaxNewsPosition(final Callback<Integer> callback)
|
||||||
|
{
|
||||||
|
if (callback == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(GetPlugin(), new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
final Integer position = _repository.retrieveMaxNewsPosition();
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTask(GetPlugin(), new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
callback.run(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void NewsUpdate(UpdateEvent event)
|
||||||
|
{
|
||||||
|
if (event.getType() != UpdateType.MIN_04)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RefreshNews();
|
||||||
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void FlightUpdate(UpdateEvent event)
|
public void FlightUpdate(UpdateEvent event)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user