mirror of
https://github.com/sekwah41/Advanced-Portals.git
synced 2024-11-25 12:06:17 +01:00
Adding velocity support
This commit is contained in:
parent
2063c8e30c
commit
1796945896
@ -31,7 +31,13 @@ Once the recode is done the master branch will be releases and the dev branch wi
|
|||||||
# Usage Data
|
# Usage Data
|
||||||
Usage stats can be found here https://bstats.org/plugin/bukkit/AdvancedPortals
|
Usage stats can be found here https://bstats.org/plugin/bukkit/AdvancedPortals
|
||||||
|
|
||||||
Were available here http://mcstats.org/plugin/AdvancedPortals but mcstats is no longer working.
|
# Velocity Support
|
||||||
|
We add basic Velocity support though it isn't really a priority.
|
||||||
|
|
||||||
|
By default, the bungee: tag works without there being a plugin on velocity however for desti: to work
|
||||||
|
for now ensure that on spigot you have the bungeecord setting enabled to register the necessary packet channels.
|
||||||
|
|
||||||
|
We will look into adding better support for proxies (Especially Velocity as it looks better)
|
||||||
|
|
||||||
# API
|
# API
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ repositories {
|
|||||||
maven { url "https://repo.maven.apache.org/maven2" }
|
maven { url "https://repo.maven.apache.org/maven2" }
|
||||||
maven { url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" }
|
maven { url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" }
|
||||||
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
|
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
|
||||||
|
maven { url "https://nexus.velocitypowered.com/repository/maven-public/" }
|
||||||
}
|
}
|
||||||
|
|
||||||
// includeLibs just says to include the library in the final jar
|
// includeLibs just says to include the library in the final jar
|
||||||
@ -45,6 +46,9 @@ dependencies {
|
|||||||
implementation "org.spigotmc:spigot-api:1.16.1-R0.1-SNAPSHOT"
|
implementation "org.spigotmc:spigot-api:1.16.1-R0.1-SNAPSHOT"
|
||||||
implementation "net.md-5:bungeecord-api:1.15-SNAPSHOT"
|
implementation "net.md-5:bungeecord-api:1.15-SNAPSHOT"
|
||||||
|
|
||||||
|
implementation "com.velocitypowered:velocity-api:1.1.0-SNAPSHOT"
|
||||||
|
annotationProcessor "com.velocitypowered:velocity-api:1.1.0-SNAPSHOT"
|
||||||
|
|
||||||
implementation "io.netty:netty-all:4.0.4.Final"
|
implementation "io.netty:netty-all:4.0.4.Final"
|
||||||
|
|
||||||
//compile fileTree(dir: 'libs', include: ['*.jar'])
|
//compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
@ -8,6 +8,7 @@ import com.sekwah.advancedportals.bukkit.effects.WarpEffects;
|
|||||||
import com.sekwah.advancedportals.bukkit.listeners.*;
|
import com.sekwah.advancedportals.bukkit.listeners.*;
|
||||||
import com.sekwah.advancedportals.bukkit.metrics.Metrics;
|
import com.sekwah.advancedportals.bukkit.metrics.Metrics;
|
||||||
import com.sekwah.advancedportals.bukkit.portals.Portal;
|
import com.sekwah.advancedportals.bukkit.portals.Portal;
|
||||||
|
import com.sekwah.advancedportals.bungee.BungeeMessages;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
@ -18,8 +19,6 @@ public class AdvancedPortalsPlugin extends JavaPlugin {
|
|||||||
//public CraftBukkit compat = null;
|
//public CraftBukkit compat = null;
|
||||||
private Settings settings;
|
private Settings settings;
|
||||||
|
|
||||||
public String channelName = "mc:advancedportals";
|
|
||||||
|
|
||||||
public boolean registeredBungeeChannels = false;
|
public boolean registeredBungeeChannels = false;
|
||||||
|
|
||||||
public HashMap<String, String> PlayerDestiMap = new HashMap<>();
|
public HashMap<String, String> PlayerDestiMap = new HashMap<>();
|
||||||
@ -110,8 +109,8 @@ public class AdvancedPortalsPlugin extends JavaPlugin {
|
|||||||
if(this.checkIfBungee()) {
|
if(this.checkIfBungee()) {
|
||||||
this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", new BungeeListener(this));
|
this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", new BungeeListener(this));
|
||||||
|
|
||||||
this.getServer().getMessenger().registerOutgoingPluginChannel(this, channelName);
|
this.getServer().getMessenger().registerOutgoingPluginChannel(this, BungeeMessages.CHANNEL_NAME);
|
||||||
this.getServer().getMessenger().registerIncomingPluginChannel(this, channelName, new PluginMessageReceiver(this));
|
this.getServer().getMessenger().registerIncomingPluginChannel(this, BungeeMessages.CHANNEL_NAME, new PluginMessageReceiver(this));
|
||||||
registeredBungeeChannels = true;
|
registeredBungeeChannels = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -152,7 +152,7 @@ public class Destination {
|
|||||||
|
|
||||||
if (PORTAL_MESSAGE_DISPLAY == 1) {
|
if (PORTAL_MESSAGE_DISPLAY == 1) {
|
||||||
player.sendMessage("");
|
player.sendMessage("");
|
||||||
player.sendMessage(PluginMessages.customPrefixFail + "\u00A7a You have been warped to \u00A7e" + name.replaceAll("_", " ") + "\u00A7a.");
|
player.sendMessage(PluginMessages.customPrefix + "\u00A7a You have been warped to \u00A7e" + name.replaceAll("_", " ") + "\u00A7a.");
|
||||||
player.sendMessage("");
|
player.sendMessage("");
|
||||||
} else if (PORTAL_MESSAGE_DISPLAY == 2 && !hideActionbar) {
|
} else if (PORTAL_MESSAGE_DISPLAY == 2 && !hideActionbar) {
|
||||||
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText("\u00A7aYou have warped to \u00A7e" + name.replaceAll("_", " ") + "\u00A7a."));
|
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText("\u00A7aYou have warped to \u00A7e" + name.replaceAll("_", " ") + "\u00A7a."));
|
||||||
|
@ -22,7 +22,7 @@ public class PluginMessageReceiver implements PluginMessageListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
|
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
|
||||||
|
|
||||||
if (!channel.equals(plugin.channelName)) {
|
if (!channel.equals(BungeeMessages.CHANNEL_NAME)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,12 +163,12 @@ public class Portal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String create(Location pos1, Location pos2, String name, String destination,
|
public static String create(Location pos1, Location pos2, String name, String destination,
|
||||||
Set<Material> triggerBlocks, PortalArg... extraData) {
|
Set<Material> triggerBlocks, PortalArg... extraData) {
|
||||||
return create(pos1, pos2, name, destination, triggerBlocks, null, extraData);
|
return create(pos1, pos2, name, destination, triggerBlocks, null, extraData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String create(Location pos1, Location pos2, String name, String destination,
|
public static String create(Location pos1, Location pos2, String name, String destination,
|
||||||
Set<Material> triggerBlocks, String serverName, PortalArg... portalArgs) {
|
Set<Material> triggerBlocks, String serverName, PortalArg... portalArgs) {
|
||||||
|
|
||||||
if (!pos1.getWorld().equals(pos2.getWorld())) {
|
if (!pos1.getWorld().equals(pos2.getWorld())) {
|
||||||
plugin.getLogger().log(Level.WARNING, "pos1 and pos2 must be in the same world!");
|
plugin.getLogger().log(Level.WARNING, "pos1 and pos2 must be in the same world!");
|
||||||
@ -247,11 +247,11 @@ public class Portal {
|
|||||||
if (portalsActive) {
|
if (portalsActive) {
|
||||||
int portalId = 0;
|
int portalId = 0;
|
||||||
for (@SuppressWarnings("unused")
|
for (@SuppressWarnings("unused")
|
||||||
Object portal : Portal.portals) {
|
Object portal : Portal.portals) {
|
||||||
if (portals[portalId].getWorldName().equals(pos2.getWorld().getName())) { // checks that the cubes arnt
|
if (portals[portalId].getWorldName().equals(pos2.getWorld().getName())) { // checks that the cubes arnt
|
||||||
// overlapping by seeing if
|
// overlapping by seeing if
|
||||||
// all 4 corners are not in
|
// all 4 corners are not in
|
||||||
// side another
|
// side another
|
||||||
if (checkOverLapPortal(pos1, pos2, portals[portalId].getPos1().getBlockX(),
|
if (checkOverLapPortal(pos1, pos2, portals[portalId].getPos1().getBlockX(),
|
||||||
portals[portalId].getPos1().getBlockY(), portals[portalId].getPos1().getBlockZ())) {
|
portals[portalId].getPos1().getBlockY(), portals[portalId].getPos1().getBlockZ())) {
|
||||||
return true;
|
return true;
|
||||||
@ -308,7 +308,7 @@ public class Portal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String create(Location pos1, Location pos2, String name, String destination, String serverName,
|
public static String create(Location pos1, Location pos2, String name, String destination, String serverName,
|
||||||
PortalArg... extraData) { // add stuff for destination names or coordinates
|
PortalArg... extraData) { // add stuff for destination names or coordinates
|
||||||
ConfigAccessor config = new ConfigAccessor(plugin, "config.yml");
|
ConfigAccessor config = new ConfigAccessor(plugin, "config.yml");
|
||||||
|
|
||||||
Material triggerBlockType;
|
Material triggerBlockType;
|
||||||
@ -421,12 +421,20 @@ public class Portal {
|
|||||||
|
|
||||||
String permission = portal.getArg("permission");
|
String permission = portal.getArg("permission");
|
||||||
|
|
||||||
|
boolean invertPermission = false;
|
||||||
|
if(permission != null) {
|
||||||
|
invertPermission = permission.startsWith("!");
|
||||||
|
if (invertPermission) {
|
||||||
|
permission.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean noMessage = permission != null && permission.startsWith("nomsg.");
|
boolean noMessage = permission != null && permission.startsWith("nomsg.");
|
||||||
if(noMessage) {
|
if(noMessage) {
|
||||||
permission.substring(6);
|
permission.substring(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(permission == null || player.hasPermission(permission) || player.isOp())) {
|
if (!(permission == null || ((!invertPermission && player.hasPermission(permission)) || (invertPermission && !player.hasPermission(permission))) || player.isOp())) {
|
||||||
if(!noMessage) {
|
if(!noMessage) {
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
PluginMessages.customPrefixFail + "\u00A7c You do not have permission to use this portal!");
|
PluginMessages.customPrefixFail + "\u00A7c You do not have permission to use this portal!");
|
||||||
@ -507,7 +515,7 @@ public class Portal {
|
|||||||
outForList.writeUTF(portal.getDestiation());
|
outForList.writeUTF(portal.getDestiation());
|
||||||
outForList.writeUTF(player.getUniqueId().toString());
|
outForList.writeUTF(player.getUniqueId().toString());
|
||||||
|
|
||||||
player.sendPluginMessage(plugin, plugin.channelName, outForList.toByteArray());
|
player.sendPluginMessage(plugin, BungeeMessages.CHANNEL_NAME, outForList.toByteArray());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
plugin.getLogger().log(Level.WARNING, "You do not have bungee setup correctly. Cross server destinations won't work.");
|
plugin.getLogger().log(Level.WARNING, "You do not have bungee setup correctly. Cross server destinations won't work.");
|
||||||
@ -534,23 +542,21 @@ public class Portal {
|
|||||||
throwPlayerBack(player);
|
throwPlayerBack(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (showFailMessage) {
|
||||||
if (showFailMessage) {
|
player.sendMessage(PluginMessages.customPrefixFail
|
||||||
player.sendMessage(PluginMessages.customPrefixFail
|
+ "\u00A7c The portal you are trying to use doesn't have a destination!");
|
||||||
+ "\u00A7c The portal you are trying to use doesn't have a destination!");
|
plugin.getLogger().log(Level.SEVERE, "The portal '" + portal.getName() + "' has just had a warp "
|
||||||
plugin.getLogger().log(Level.SEVERE, "The portal '" + portal.getName() + "' has just had a warp "
|
+ "attempt and either the data is corrupt or portal doesn't exist!");
|
||||||
+ "attempt and either the data is corrupt or portal doesn't exist!");
|
if(doKnockback)
|
||||||
if(doKnockback)
|
throwPlayerBack(player);
|
||||||
throwPlayerBack(player);
|
failSound(player, portal);
|
||||||
failSound(player, portal);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (portal.hasArg("command.1")) {
|
if (portal.hasArg("command.1")) {
|
||||||
warped = true;
|
warped = true;
|
||||||
int commandLine = 1;
|
int commandLine = 1;
|
||||||
String command = portal.getArg("command." + commandLine);// portalData.getConfig().getString(portal.getName()+
|
String command = portal.getArg("command." + commandLine);// portalData.getConfig().getString(portal.getName()+
|
||||||
// ".portalArgs.command." + commandLine);
|
// ".portalArgs.command." + commandLine);
|
||||||
do {
|
do {
|
||||||
// (?i) makes the search case insensitive
|
// (?i) makes the search case insensitive
|
||||||
command = command.replaceAll("@player", player.getName());
|
command = command.replaceAll("@player", player.getName());
|
||||||
@ -588,15 +594,15 @@ public class Portal {
|
|||||||
ByteArrayDataOutput outForList = ByteStreams.newDataOutput();
|
ByteArrayDataOutput outForList = ByteStreams.newDataOutput();
|
||||||
outForList.writeUTF(BungeeMessages.BUNGEE_COMMAND);
|
outForList.writeUTF(BungeeMessages.BUNGEE_COMMAND);
|
||||||
outForList.writeUTF(command);
|
outForList.writeUTF(command);
|
||||||
player.sendPluginMessage(plugin, plugin.channelName, outForList.toByteArray());
|
player.sendPluginMessage(plugin, BungeeMessages.CHANNEL_NAME, outForList.toByteArray());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
plugin.getLogger().log(Level.WARNING, "You do not have bungee setup correctly. For security advanced bungee features won't work.");
|
plugin.getLogger().log(Level.WARNING, "You do not have bungee setup correctly. For security advanced bungee features won't work.");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
player.chat("/" + command);
|
player.chat("/" + command);
|
||||||
// player.performCommand(command);
|
// player.performCommand(command);
|
||||||
}
|
}
|
||||||
command = portal.getArg("command." + ++commandLine);
|
command = portal.getArg("command." + ++commandLine);
|
||||||
} while (command != null);
|
} while (command != null);
|
||||||
|
@ -8,17 +8,15 @@ import java.util.HashMap;
|
|||||||
|
|
||||||
public class AdvancedPortalsPlugin extends Plugin {
|
public class AdvancedPortalsPlugin extends Plugin {
|
||||||
|
|
||||||
public String channelName = "mc:advancedportals";
|
|
||||||
|
|
||||||
public HashMap<String, String[]> PlayerDestiMap = new HashMap<>();
|
public HashMap<String, String[]> PlayerDestiMap = new HashMap<>();
|
||||||
// key: UUID (string)
|
// key: UUID (string)
|
||||||
// value: [0] targetServer, [1] targetDestination
|
// value: [0] targetServer, [1] targetDestination
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
getProxy().registerChannel(channelName);
|
getProxy().registerChannel(BungeeMessages.CHANNEL_NAME);
|
||||||
|
|
||||||
if(channelName != null)
|
if(BungeeMessages.CHANNEL_NAME != null)
|
||||||
|
|
||||||
getProxy().getPluginManager().registerListener(this, new PluginMessageReceiver(this));
|
getProxy().getPluginManager().registerListener(this, new PluginMessageReceiver(this));
|
||||||
getProxy().getPluginManager().registerListener(this, new EventListener(this));
|
getProxy().getPluginManager().registerListener(this, new EventListener(this));
|
||||||
|
@ -17,6 +17,8 @@ public class BungeeMessages {
|
|||||||
*/
|
*/
|
||||||
public static String ENTER_PORTAL = "PortalEnter";
|
public static String ENTER_PORTAL = "PortalEnter";
|
||||||
|
|
||||||
|
public static String CHANNEL_NAME = "advancedportals:warp";
|
||||||
|
|
||||||
|
|
||||||
public static String SERVER_DESTI = "BungeePortal";
|
public static String SERVER_DESTI = "BungeePortal";
|
||||||
|
|
||||||
|
@ -8,9 +8,6 @@ import net.md_5.bungee.api.event.ServerConnectedEvent;
|
|||||||
import net.md_5.bungee.api.plugin.Listener;
|
import net.md_5.bungee.api.plugin.Listener;
|
||||||
import net.md_5.bungee.event.EventHandler;
|
import net.md_5.bungee.event.EventHandler;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class EventListener implements Listener {
|
public class EventListener implements Listener {
|
||||||
private AdvancedPortalsPlugin plugin;
|
private AdvancedPortalsPlugin plugin;
|
||||||
|
|
||||||
@ -35,7 +32,7 @@ public class EventListener implements Listener {
|
|||||||
out.writeUTF(val[1]);
|
out.writeUTF(val[1]);
|
||||||
out.writeUTF(val[2]);
|
out.writeUTF(val[2]);
|
||||||
|
|
||||||
event.getServer().sendData(plugin.channelName, out.toByteArray());
|
event.getServer().sendData(BungeeMessages.CHANNEL_NAME, out.toByteArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,6 @@ import net.md_5.bungee.api.connection.Server;
|
|||||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||||
import net.md_5.bungee.api.plugin.Listener;
|
import net.md_5.bungee.api.plugin.Listener;
|
||||||
import net.md_5.bungee.event.EventHandler;
|
import net.md_5.bungee.event.EventHandler;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class PluginMessageReceiver implements Listener {
|
public class PluginMessageReceiver implements Listener {
|
||||||
@ -21,7 +19,7 @@ public class PluginMessageReceiver implements Listener {
|
|||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onMessageReceived(PluginMessageEvent event) {
|
public void onMessageReceived(PluginMessageEvent event) {
|
||||||
if(!event.getTag().equalsIgnoreCase(plugin.channelName) || !(event.getSender() instanceof Server)) return;
|
if(!event.getTag().equalsIgnoreCase(BungeeMessages.CHANNEL_NAME) || !(event.getSender() instanceof Server)) return;
|
||||||
|
|
||||||
ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
|
ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
|
||||||
String subChannel = in.readUTF();
|
String subChannel = in.readUTF();
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.sekwah.advancedportals.velocity;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.sekwah.advancedportals.bungee.BungeeMessages;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
|
import com.velocitypowered.api.plugin.Plugin;
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
@Plugin(id = "advancedportals", name = "Advanced Portals",
|
||||||
|
url = "https://www.spigotmc.org/resources/advanced-portals.14356/",
|
||||||
|
version = "0.5.12")
|
||||||
|
public class AdvancedPortalsPlugin {
|
||||||
|
|
||||||
|
public HashMap<String, String[]> PlayerDestiMap = new HashMap<>();
|
||||||
|
|
||||||
|
private final Logger logger;
|
||||||
|
private ProxyServer proxy;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AdvancedPortalsPlugin(ProxyServer proxy, Logger logger) {
|
||||||
|
|
||||||
|
this.proxy = proxy;
|
||||||
|
this.logger = logger;
|
||||||
|
|
||||||
|
String[] splitChannel = BungeeMessages.CHANNEL_NAME.split(":");
|
||||||
|
proxy.getChannelRegistrar().register(MinecraftChannelIdentifier.create(splitChannel[0], splitChannel[1]));
|
||||||
|
|
||||||
|
logger.info("\u00A7aAdvanced portals have been successfully enabled!");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPluginMessage(PluginMessageEvent event) {
|
||||||
|
System.out.println("THING HERE");
|
||||||
|
System.out.println(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
main: com.sekwah.advancedportals.bungee.AdvancedPortalsPlugin
|
main: com.sekwah.advancedportals.bungee.AdvancedPortalsPlugin
|
||||||
name: AdvancedPortals
|
name: AdvancedPortals
|
||||||
version: 0.5.11
|
version: 0.5.12
|
||||||
author: sekwah41
|
author: sekwah41
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
main: com.sekwah.advancedportals.bukkit.AdvancedPortalsPlugin
|
main: com.sekwah.advancedportals.bukkit.AdvancedPortalsPlugin
|
||||||
name: AdvancedPortals
|
name: AdvancedPortals
|
||||||
version: 0.5.11
|
version: 0.5.12
|
||||||
author: sekwah41
|
author: sekwah41
|
||||||
description: An advanced portals plugin for bukkit.
|
description: An advanced portals plugin for bukkit.
|
||||||
api-version: 1.13
|
api-version: 1.13
|
||||||
|
Loading…
Reference in New Issue
Block a user