Merge pull request #1274 from N0tMyFaultOG/master
Switching from hastebin to Incendo
This commit is contained in:
commit
70e24e432c
@ -109,5 +109,6 @@ subprojects {
|
|||||||
maven {url "http://maven.sk89q.com/artifactory/repo"}
|
maven {url "http://maven.sk89q.com/artifactory/repo"}
|
||||||
maven {url "http://repo.spongepowered.org/maven"}
|
maven {url "http://repo.spongepowered.org/maven"}
|
||||||
maven {url "http://dl.bintray.com/tastybento/maven-repo"}
|
maven {url "http://dl.bintray.com/tastybento/maven-repo"}
|
||||||
|
maven {url "https://repo.inventivetalent.org/content/groups/public/"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ dependencies {
|
|||||||
compile 'org.bukkit.craftbukkit:CraftBukkit:1.8.8'
|
compile 'org.bukkit.craftbukkit:CraftBukkit:1.8.8'
|
||||||
compile 'com.comphenix.protocol:ProtocolLib-API:4.4.0-SNAPSHOT'
|
compile 'com.comphenix.protocol:ProtocolLib-API:4.4.0-SNAPSHOT'
|
||||||
compile 'com.wasteofplastic:askyblock:3.0.8.2'
|
compile 'com.wasteofplastic:askyblock:3.0.8.2'
|
||||||
compile('org.inventivetalent:mapmanager:1.7.1-SNAPSHOT') {
|
compile('org.inventivetalent:mapmanager:1.7.2-SNAPSHOT') {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
package com.boydti.fawe.util;
|
|
||||||
|
|
||||||
import com.boydti.fawe.Fawe;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class HastebinUtility {
|
|
||||||
|
|
||||||
public static final String BIN_URL = "https://hastebin.com/documents", USER_AGENT = "Mozilla/5.0";
|
|
||||||
public static final Pattern PATTERN = Pattern.compile("\\{\"key\":\"([\\S\\s]*)\"\\}");
|
|
||||||
|
|
||||||
public static String upload(final String string) throws IOException {
|
|
||||||
final URL url = new URL(BIN_URL);
|
|
||||||
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
||||||
|
|
||||||
connection.setRequestMethod("POST");
|
|
||||||
connection.setRequestProperty("User-Agent", USER_AGENT);
|
|
||||||
connection.setDoOutput(true);
|
|
||||||
|
|
||||||
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
|
|
||||||
outputStream.write(string.getBytes());
|
|
||||||
outputStream.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder response;
|
|
||||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
|
||||||
response = new StringBuilder();
|
|
||||||
|
|
||||||
String inputLine;
|
|
||||||
while ((inputLine = in.readLine()) != null) {
|
|
||||||
response.append(inputLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Matcher matcher = PATTERN.matcher(response.toString());
|
|
||||||
if (matcher.matches()) {
|
|
||||||
return "https://hastebin.com/" + matcher.group(1);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Couldn't read response!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String upload(final File file) throws IOException {
|
|
||||||
final StringBuilder content = new StringBuilder();
|
|
||||||
List<String> lines = new ArrayList<>();
|
|
||||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
|
||||||
String line;
|
|
||||||
int i = 0;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
lines.add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = Math.max(0, lines.size() - 1000); i < lines.size(); i++) {
|
|
||||||
content.append(lines.get(i)).append("\n");
|
|
||||||
}
|
|
||||||
return upload(content.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String debugPaste() throws IOException {
|
|
||||||
String settingsYML = HastebinUtility.upload(new File(Fawe.imp().getDirectory(), "config.yml"));
|
|
||||||
String messagesYML = HastebinUtility.upload(new File(Fawe.imp().getDirectory(), "message.yml"));
|
|
||||||
String commandsYML = HastebinUtility.upload(new File(Fawe.imp().getDirectory(), "commands.yml"));
|
|
||||||
String latestLOG;
|
|
||||||
try {
|
|
||||||
latestLOG = HastebinUtility.upload(new File(Fawe.imp().getDirectory(), "../../logs/latest.log"));
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
latestLOG = "too big :(";
|
|
||||||
}
|
|
||||||
StringBuilder b = new StringBuilder();
|
|
||||||
b.append(
|
|
||||||
"# Welcome to this paste\n# It is meant to provide us at IntellectualSites with better information about your "
|
|
||||||
+ "problem\n\n# We will start with some informational files\n");
|
|
||||||
b.append("links.config_yml: ").append(settingsYML).append('\n');
|
|
||||||
b.append("links.messages_yml: ").append(messagesYML).append('\n');
|
|
||||||
b.append("links.commands_yml: ").append(commandsYML).append('\n');
|
|
||||||
b.append("links.latest_log: ").append(latestLOG).append('\n');
|
|
||||||
b.append("\n# Server Information\n");
|
|
||||||
b.append("server.platform: ").append(Fawe.imp().getPlatform()).append('\n');
|
|
||||||
b.append(Fawe.imp().getDebugInfo()).append('\n');
|
|
||||||
b.append("\n\n# YAY! Now, let's see what we can find in your JVM\n");
|
|
||||||
Runtime runtime = Runtime.getRuntime();
|
|
||||||
b.append("memory.free: ").append(runtime.freeMemory()).append('\n');
|
|
||||||
b.append("memory.max: ").append(runtime.maxMemory()).append('\n');
|
|
||||||
b.append("java.specification.version: '").append(System.getProperty("java.specification.version")).append("'\n");
|
|
||||||
b.append("java.vendor: '").append(System.getProperty("java.vendor")).append("'\n");
|
|
||||||
b.append("java.version: '").append(System.getProperty("java.version")).append("'\n");
|
|
||||||
b.append("os.arch: '").append(System.getProperty("os.arch")).append("'\n");
|
|
||||||
b.append("os.name: '").append(System.getProperty("os.name")).append("'\n");
|
|
||||||
b.append("os.version: '").append(System.getProperty("os.version")).append("'\n\n");
|
|
||||||
b.append("# Okay :D Great. You are now ready to create your bug report!");
|
|
||||||
b.append("\n# You can do so at https://github.com/boy0001/FastAsyncWorldedit/issues");
|
|
||||||
|
|
||||||
String link = HastebinUtility.upload(b.toString());
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
260
core/src/main/java/com/boydti/fawe/util/IncendoPaster.java
Normal file
260
core/src/main/java/com/boydti/fawe/util/IncendoPaster.java
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
package com.boydti.fawe.util;
|
||||||
|
|
||||||
|
import com.boydti.fawe.Fawe;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single class paster for the Incendo paste service
|
||||||
|
*
|
||||||
|
* @author Sauilitired
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||||
|
public final class IncendoPaster {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload service URL
|
||||||
|
*/
|
||||||
|
public static final String UPLOAD_PATH = "https://incendo.org/paste/upload";
|
||||||
|
/**
|
||||||
|
* Valid paste applications
|
||||||
|
*/
|
||||||
|
public static final Collection<String>
|
||||||
|
VALID_APPLICATIONS = Arrays
|
||||||
|
.asList("plotsquared", "fastasyncworldedit", "incendopermissions", "kvantum");
|
||||||
|
|
||||||
|
private final Collection<PasteFile> files = new ArrayList<>();
|
||||||
|
private final String pasteApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new paster
|
||||||
|
*
|
||||||
|
* @param pasteApplication The application that is sending the paste
|
||||||
|
*/
|
||||||
|
public IncendoPaster(final String pasteApplication) {
|
||||||
|
if (pasteApplication == null || pasteApplication.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("paste application cannot be null, nor empty");
|
||||||
|
}
|
||||||
|
if (!VALID_APPLICATIONS.contains(pasteApplication.toLowerCase(Locale.ENGLISH))) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Unknown application name: %s", pasteApplication));
|
||||||
|
}
|
||||||
|
this.pasteApplication = pasteApplication;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an immutable collection containing all the files that have been added to this paster
|
||||||
|
*
|
||||||
|
* @return Unmodifiable collection
|
||||||
|
*/
|
||||||
|
public final Collection<PasteFile> getFiles() {
|
||||||
|
return Collections.unmodifiableCollection(this.files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a file to the paster
|
||||||
|
*
|
||||||
|
* @param file File to paste
|
||||||
|
*/
|
||||||
|
public void addFile(final PasteFile file) {
|
||||||
|
if (file == null) {
|
||||||
|
throw new IllegalArgumentException("File cannot be null");
|
||||||
|
}
|
||||||
|
// Check to see that no duplicate files are submitted
|
||||||
|
for (final PasteFile pasteFile : this.files) {
|
||||||
|
if (pasteFile.fileName.equalsIgnoreCase(file.getFileName())) {
|
||||||
|
throw new IllegalArgumentException(String.format("Found duplicate file with name %s",
|
||||||
|
file.getFileName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.files.add(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a JSON string from the submitted information
|
||||||
|
*
|
||||||
|
* @return compiled JSON string
|
||||||
|
*/
|
||||||
|
private String toJsonString() {
|
||||||
|
final StringBuilder builder = new StringBuilder("{\n");
|
||||||
|
builder.append("\"paste_application\": \"").append(this.pasteApplication).append("\",\n\"files\": \"");
|
||||||
|
Iterator<PasteFile> fileIterator = this.files.iterator();
|
||||||
|
while (fileIterator.hasNext()) {
|
||||||
|
final PasteFile file = fileIterator.next();
|
||||||
|
builder.append(file.getFileName());
|
||||||
|
if (fileIterator.hasNext()) {
|
||||||
|
builder.append(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.append("\",\n");
|
||||||
|
fileIterator = this.files.iterator();
|
||||||
|
while (fileIterator.hasNext()) {
|
||||||
|
final PasteFile file = fileIterator.next();
|
||||||
|
builder.append("\"file-").append(file.getFileName()).append("\": \"")
|
||||||
|
.append(file.getContent().replaceAll("\"", "\\\\\"")).append("\"");
|
||||||
|
if (fileIterator.hasNext()) {
|
||||||
|
builder.append(",\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.append("\n}");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload the paste and return the status message
|
||||||
|
*
|
||||||
|
* @return Status message
|
||||||
|
* @throws Throwable any and all exceptions
|
||||||
|
*/
|
||||||
|
public final String upload() throws Throwable {
|
||||||
|
final URL url = new URL(UPLOAD_PATH);
|
||||||
|
final URLConnection connection = url.openConnection();
|
||||||
|
final HttpURLConnection httpURLConnection = (HttpURLConnection) connection;
|
||||||
|
httpURLConnection.setRequestMethod("POST");
|
||||||
|
httpURLConnection.setDoOutput(true);
|
||||||
|
final byte[] content = toJsonString().getBytes(Charsets.UTF_8);
|
||||||
|
httpURLConnection.setFixedLengthStreamingMode(content.length);
|
||||||
|
httpURLConnection.setRequestProperty("Content-Type", "application/json");
|
||||||
|
httpURLConnection.setRequestProperty("Accept", "*/*");
|
||||||
|
httpURLConnection.connect();
|
||||||
|
try (final OutputStream stream = httpURLConnection.getOutputStream()) {
|
||||||
|
stream.write(content);
|
||||||
|
}
|
||||||
|
if (!httpURLConnection.getResponseMessage().contains("OK")) {
|
||||||
|
throw new IllegalStateException(String.format("Server returned status: %d %s",
|
||||||
|
httpURLConnection.getResponseCode(), httpURLConnection.getResponseMessage()));
|
||||||
|
}
|
||||||
|
final StringBuilder input = new StringBuilder();
|
||||||
|
try (final BufferedReader inputStream = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()))) {
|
||||||
|
String line;
|
||||||
|
while ((line = inputStream.readLine()) != null) {
|
||||||
|
input.append(line).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return input.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple class that represents a paste file
|
||||||
|
*/
|
||||||
|
public static class PasteFile {
|
||||||
|
|
||||||
|
private final String fileName;
|
||||||
|
private final String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new paste file
|
||||||
|
*
|
||||||
|
* @param fileName File name, cannot be empty, nor null
|
||||||
|
* @param content File content, cannot be empty, nor null
|
||||||
|
*/
|
||||||
|
public PasteFile(final String fileName, final String content) {
|
||||||
|
if (fileName == null || fileName.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("file name cannot be null, nor empty");
|
||||||
|
}
|
||||||
|
if (content == null || content.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("content cannot be null, nor empty");
|
||||||
|
}
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file name
|
||||||
|
*
|
||||||
|
* @return File name
|
||||||
|
*/
|
||||||
|
public String getFileName() {
|
||||||
|
return this.fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file content as a single string
|
||||||
|
*
|
||||||
|
* @return File content
|
||||||
|
*/
|
||||||
|
public String getContent() {
|
||||||
|
return this.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String debugPaste() throws IOException {
|
||||||
|
final IncendoPaster incendoPaster = new IncendoPaster("fastasyncworldedit");
|
||||||
|
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
b.append(
|
||||||
|
"# Welcome to this paste\n# It is meant to provide us at IntellectualSites with better information about your "
|
||||||
|
+ "problem\n");
|
||||||
|
b.append("\n# Server Information\n");
|
||||||
|
b.append("server.platform: ").append(Fawe.imp().getPlatform()).append('\n');
|
||||||
|
b.append(Fawe.imp().getDebugInfo()).append('\n');
|
||||||
|
b.append("\n\n# YAY! Now, let's see what we can find in your JVM\n");
|
||||||
|
Runtime runtime = Runtime.getRuntime();
|
||||||
|
b.append("memory.free: ").append(runtime.freeMemory()).append('\n');
|
||||||
|
b.append("memory.max: ").append(runtime.maxMemory()).append('\n');
|
||||||
|
b.append("java.specification.version: '").append(System.getProperty("java.specification.version")).append("'\n");
|
||||||
|
b.append("java.vendor: '").append(System.getProperty("java.vendor")).append("'\n");
|
||||||
|
b.append("java.version: '").append(System.getProperty("java.version")).append("'\n");
|
||||||
|
b.append("os.arch: '").append(System.getProperty("os.arch")).append("'\n");
|
||||||
|
b.append("os.name: '").append(System.getProperty("os.name")).append("'\n");
|
||||||
|
b.append("os.version: '").append(System.getProperty("os.version")).append("'\n\n");
|
||||||
|
b.append("# Okay :D Great. You are now ready to create your bug report!");
|
||||||
|
b.append("\n# You can do so at https://github.com/boy0001/FastAsyncWorldedit/issues");
|
||||||
|
b.append("\n# or via our Discord at https://discord.gg/ngZCzbU");
|
||||||
|
incendoPaster.addFile(new IncendoPaster.PasteFile("information", b.toString()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
final File logFile = new File(Fawe.imp().getDirectory(), "../../logs/latest.log");
|
||||||
|
final String file;
|
||||||
|
if (Files.size(logFile.toPath()) > 14_000_000) {
|
||||||
|
file = "too big :(";
|
||||||
|
} else {
|
||||||
|
file = readFile(logFile);
|
||||||
|
}
|
||||||
|
incendoPaster.addFile(new IncendoPaster.PasteFile("latest.log", file));
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
incendoPaster.addFile(new PasteFile("config.yml", readFile(new File(Fawe.imp().getDirectory(), "config.yml"))));
|
||||||
|
incendoPaster.addFile(new PasteFile("message.yml", readFile(new File(Fawe.imp().getDirectory(), "message.yml"))));
|
||||||
|
|
||||||
|
final String rawResponse;
|
||||||
|
try {
|
||||||
|
rawResponse = incendoPaster.upload();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
throw new IOException(String.format("Failed to create the debug paste: %s", throwable.getMessage()), throwable);
|
||||||
|
}
|
||||||
|
final JsonObject jsonObject = new JsonParser().parse(rawResponse).getAsJsonObject();
|
||||||
|
|
||||||
|
if (jsonObject.has("created")) {
|
||||||
|
final String pasteId = jsonObject.get("paste_id").getAsString();
|
||||||
|
return String.format("https://incendo.org/paste/view/%s", pasteId);
|
||||||
|
} else {
|
||||||
|
throw new IOException(String.format("Failed to create the debug paste: %s",
|
||||||
|
jsonObject.get("response").getAsString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readFile(final File file) throws IOException {
|
||||||
|
final StringBuilder content = new StringBuilder();
|
||||||
|
final List<String> lines = new ArrayList<>();
|
||||||
|
try (final BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
lines.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = Math.max(0, lines.size() - 1000); i < lines.size(); i++) {
|
||||||
|
content.append(lines.get(i)).append("\n");
|
||||||
|
}
|
||||||
|
return content.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -24,10 +24,7 @@ import com.boydti.fawe.FaweVersion;
|
|||||||
import com.boydti.fawe.config.BBC;
|
import com.boydti.fawe.config.BBC;
|
||||||
import com.boydti.fawe.config.Settings;
|
import com.boydti.fawe.config.Settings;
|
||||||
import com.boydti.fawe.object.FawePlayer;
|
import com.boydti.fawe.object.FawePlayer;
|
||||||
import com.boydti.fawe.util.HastebinUtility;
|
import com.boydti.fawe.util.*;
|
||||||
import com.boydti.fawe.util.StringMan;
|
|
||||||
import com.boydti.fawe.util.TaskManager;
|
|
||||||
import com.boydti.fawe.util.Updater;
|
|
||||||
import com.sk89q.minecraft.util.commands.Command;
|
import com.sk89q.minecraft.util.commands.Command;
|
||||||
import com.sk89q.minecraft.util.commands.CommandContext;
|
import com.sk89q.minecraft.util.commands.CommandContext;
|
||||||
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
||||||
@ -185,13 +182,13 @@ public class WorldEditCommands {
|
|||||||
@Command(
|
@Command(
|
||||||
aliases = {"debugpaste"},
|
aliases = {"debugpaste"},
|
||||||
usage = "",
|
usage = "",
|
||||||
desc = "Upload debug information to hastebin.com",
|
desc = "Upload latest.log, config.yml and your message.yml to https://incendo.org",
|
||||||
min = 0,
|
min = 0,
|
||||||
max = 0
|
max = 0
|
||||||
)
|
)
|
||||||
@CommandPermissions("worldedit.debugpaste")
|
@CommandPermissions("worldedit.debugpaste")
|
||||||
public void debugpaste(Actor actor) throws WorldEditException, IOException {
|
public void debugpaste(Actor actor) throws WorldEditException, IOException {
|
||||||
BBC.DOWNLOAD_LINK.send(actor, HastebinUtility.debugPaste());
|
BBC.DOWNLOAD_LINK.send(actor, IncendoPaster.debugPaste());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Fri Jan 12 10:28:44 AEDT 2018
|
#Tue Feb 26 14:36:53 CET 2019
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
|
||||||
|
Loading…
Reference in New Issue
Block a user