geforkt von Mirrors/Velocity
Add Forge mod list support
Dieser Commit ist enthalten in:
Ursprung
7578aa27a9
Commit
c5a27bb135
@ -0,0 +1,23 @@
|
|||||||
|
package com.velocitypowered.api.event.player;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.util.ModInfo;
|
||||||
|
|
||||||
|
public final class PlayerModInfoEvent {
|
||||||
|
private final Player player;
|
||||||
|
private final ModInfo modInfo;
|
||||||
|
|
||||||
|
public PlayerModInfoEvent(Player player, ModInfo modInfo) {
|
||||||
|
this.player = Preconditions.checkNotNull(player, "player");
|
||||||
|
this.modInfo = Preconditions.checkNotNull(modInfo, "modInfo");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModInfo getModInfo() {
|
||||||
|
return modInfo;
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import com.velocitypowered.api.proxy.server.RegisteredServer;
|
|||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
import com.velocitypowered.api.proxy.player.TabList;
|
import com.velocitypowered.api.proxy.player.TabList;
|
||||||
import com.velocitypowered.api.util.MessagePosition;
|
import com.velocitypowered.api.util.MessagePosition;
|
||||||
|
import com.velocitypowered.api.util.ModInfo;
|
||||||
import com.velocitypowered.api.util.title.Title;
|
import com.velocitypowered.api.util.title.Title;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -45,6 +46,12 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
|
|||||||
*/
|
*/
|
||||||
PlayerSettings getPlayerSettings();
|
PlayerSettings getPlayerSettings();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the player's mod info if they have a modded client.
|
||||||
|
* @return an {@link Optional} the mod info. which may be empty
|
||||||
|
*/
|
||||||
|
Optional<ModInfo> getModInfo();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current player's ping
|
* Returns the current player's ping
|
||||||
* @return the player's ping or -1 if ping information is currently unknown
|
* @return the player's ping or -1 if ping information is currently unknown
|
||||||
|
@ -3,6 +3,7 @@ package com.velocitypowered.api.proxy.server;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.velocitypowered.api.util.Favicon;
|
import com.velocitypowered.api.util.Favicon;
|
||||||
|
import com.velocitypowered.api.util.ModInfo;
|
||||||
import net.kyori.text.Component;
|
import net.kyori.text.Component;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
@ -16,13 +17,13 @@ public final class ServerPing {
|
|||||||
private final Players players;
|
private final Players players;
|
||||||
private final Component description;
|
private final Component description;
|
||||||
private final @Nullable Favicon favicon;
|
private final @Nullable Favicon favicon;
|
||||||
private final ModInfo modinfo;
|
private final @Nullable ModInfo modinfo;
|
||||||
|
|
||||||
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon) {
|
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon) {
|
||||||
this(version, players, description, favicon, ModInfo.DEFAULT);
|
this(version, players, description, favicon, ModInfo.DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon, ServerPing.@Nullable ModInfo modinfo) {
|
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon, @Nullable ModInfo modinfo) {
|
||||||
this.version = Preconditions.checkNotNull(version, "version");
|
this.version = Preconditions.checkNotNull(version, "version");
|
||||||
this.players = players;
|
this.players = players;
|
||||||
this.description = Preconditions.checkNotNull(description, "description");
|
this.description = Preconditions.checkNotNull(description, "description");
|
||||||
@ -74,8 +75,8 @@ public final class ServerPing {
|
|||||||
builder.favicon = favicon;
|
builder.favicon = favicon;
|
||||||
builder.nullOutModinfo = modinfo == null;
|
builder.nullOutModinfo = modinfo == null;
|
||||||
if (modinfo != null) {
|
if (modinfo != null) {
|
||||||
builder.modType = modinfo.type;
|
builder.modType = modinfo.getType();
|
||||||
builder.mods.addAll(modinfo.modList);
|
builder.mods.addAll(modinfo.getMods());
|
||||||
}
|
}
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
@ -93,7 +94,7 @@ public final class ServerPing {
|
|||||||
private int maximumPlayers;
|
private int maximumPlayers;
|
||||||
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
|
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
|
||||||
private String modType;
|
private String modType;
|
||||||
private final List<Mod> mods = new ArrayList<>();
|
private final List<ModInfo.Mod> mods = new ArrayList<>();
|
||||||
private Component description;
|
private Component description;
|
||||||
private Favicon favicon;
|
private Favicon favicon;
|
||||||
private boolean nullOutPlayers;
|
private boolean nullOutPlayers;
|
||||||
@ -128,7 +129,7 @@ public final class ServerPing {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder mods(Mod... mods) {
|
public Builder mods(ModInfo.Mod... mods) {
|
||||||
this.mods.addAll(Arrays.asList(mods));
|
this.mods.addAll(Arrays.asList(mods));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -196,7 +197,7 @@ public final class ServerPing {
|
|||||||
return modType;
|
return modType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Mod> getMods() {
|
public List<ModInfo.Mod> getMods() {
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,58 +302,4 @@ public final class ServerPing {
|
|||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class ModInfo {
|
|
||||||
public static final ModInfo DEFAULT = new ModInfo("FML", ImmutableList.of());
|
|
||||||
|
|
||||||
private final String type;
|
|
||||||
private final List<Mod> modList;
|
|
||||||
|
|
||||||
public ModInfo(String type, List<Mod> modList) {
|
|
||||||
this.type = Preconditions.checkNotNull(type, "type");
|
|
||||||
this.modList = ImmutableList.copyOf(modList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Mod> getMods() {
|
|
||||||
return modList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ModInfo{" +
|
|
||||||
"type='" + type + '\'' +
|
|
||||||
", modList=" + modList +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Mod {
|
|
||||||
private final String id;
|
|
||||||
private final String version;
|
|
||||||
|
|
||||||
public Mod(String id, String version) {
|
|
||||||
this.id = Preconditions.checkNotNull(id, "id");
|
|
||||||
this.version = Preconditions.checkNotNull(version, "version");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Mod{" +
|
|
||||||
"id='" + id + '\'' +
|
|
||||||
", version='" + version + '\'' +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
60
api/src/main/java/com/velocitypowered/api/util/ModInfo.java
Normale Datei
60
api/src/main/java/com/velocitypowered/api/util/ModInfo.java
Normale Datei
@ -0,0 +1,60 @@
|
|||||||
|
package com.velocitypowered.api.util;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class ModInfo {
|
||||||
|
public static final ModInfo DEFAULT = new ModInfo("FML", ImmutableList.of());
|
||||||
|
|
||||||
|
private final String type;
|
||||||
|
private final List<Mod> modList;
|
||||||
|
|
||||||
|
public ModInfo(String type, List<Mod> modList) {
|
||||||
|
this.type = Preconditions.checkNotNull(type, "type");
|
||||||
|
this.modList = ImmutableList.copyOf(modList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Mod> getMods() {
|
||||||
|
return modList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ModInfo{" +
|
||||||
|
"type='" + type + '\'' +
|
||||||
|
", modList=" + modList +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Mod {
|
||||||
|
private final String id;
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
public Mod(String id, String version) {
|
||||||
|
this.id = Preconditions.checkNotNull(id, "id");
|
||||||
|
this.version = Preconditions.checkNotNull(version, "version");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Mod{" +
|
||||||
|
"id='" + id + '\'' +
|
||||||
|
", version='" + version + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
package com.velocitypowered.proxy.connection.client;
|
package com.velocitypowered.proxy.connection.client;
|
||||||
|
|
||||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
|
||||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerChatEvent;
|
import com.velocitypowered.api.event.player.PlayerChatEvent;
|
||||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||||
import com.velocitypowered.api.proxy.player.TabList;
|
import com.velocitypowered.api.util.ModInfo;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||||
@ -13,7 +12,6 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
|||||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||||
import com.velocitypowered.proxy.protocol.packet.*;
|
import com.velocitypowered.proxy.protocol.packet.*;
|
||||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||||
import com.velocitypowered.proxy.tablist.VelocityTabList;
|
|
||||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import net.kyori.text.TextComponent;
|
import net.kyori.text.TextComponent;
|
||||||
@ -147,6 +145,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
player.getConnectedServer().getConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
|
player.getConnectedServer().getConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
|
||||||
} else if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
|
} else if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
|
||||||
if (packet.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
if (packet.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||||
|
List<ModInfo.Mod> mods = PluginMessageUtil.readModList(packet);
|
||||||
|
if (!mods.isEmpty()) {
|
||||||
|
player.setModInfo(new ModInfo("FML", mods));
|
||||||
|
}
|
||||||
|
|
||||||
// Always forward the FML handshake to the remote server.
|
// Always forward the FML handshake to the remote server.
|
||||||
player.getConnectedServer().getConnection().write(packet);
|
player.getConnectedServer().getConnection().write(packet);
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,6 +4,7 @@ import com.google.common.base.Preconditions;
|
|||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||||
import com.velocitypowered.api.event.player.KickedFromServerEvent;
|
import com.velocitypowered.api.event.player.KickedFromServerEvent;
|
||||||
|
import com.velocitypowered.api.event.player.PlayerModInfoEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
|
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
|
||||||
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
||||||
import com.velocitypowered.api.permission.PermissionFunction;
|
import com.velocitypowered.api.permission.PermissionFunction;
|
||||||
@ -16,8 +17,8 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
|||||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
import com.velocitypowered.api.proxy.player.TabList;
|
|
||||||
import com.velocitypowered.api.util.MessagePosition;
|
import com.velocitypowered.api.util.MessagePosition;
|
||||||
|
import com.velocitypowered.api.util.ModInfo;
|
||||||
import com.velocitypowered.api.util.title.TextTitle;
|
import com.velocitypowered.api.util.title.TextTitle;
|
||||||
import com.velocitypowered.api.util.title.Title;
|
import com.velocitypowered.api.util.title.Title;
|
||||||
import com.velocitypowered.api.util.title.Titles;
|
import com.velocitypowered.api.util.title.Titles;
|
||||||
@ -31,9 +32,6 @@ import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
|||||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||||
import com.velocitypowered.proxy.protocol.packet.*;
|
import com.velocitypowered.proxy.protocol.packet.*;
|
||||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Chat;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
|
||||||
import com.velocitypowered.proxy.tablist.VelocityTabList;
|
import com.velocitypowered.proxy.tablist.VelocityTabList;
|
||||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||||
import net.kyori.text.Component;
|
import net.kyori.text.Component;
|
||||||
@ -69,6 +67,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
private VelocityServerConnection connectedServer;
|
private VelocityServerConnection connectedServer;
|
||||||
private VelocityServerConnection connectionInFlight;
|
private VelocityServerConnection connectionInFlight;
|
||||||
private PlayerSettings settings;
|
private PlayerSettings settings;
|
||||||
|
private ModInfo modInfo;
|
||||||
private final VelocityTabList tabList;
|
private final VelocityTabList tabList;
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
|
|
||||||
@ -121,6 +120,15 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
server.getEventManager().fireAndForget(new PlayerSettingsChangedEvent(this, this.settings));
|
server.getEventManager().fireAndForget(new PlayerSettingsChangedEvent(this, this.settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<ModInfo> getModInfo() {
|
||||||
|
return Optional.ofNullable(modInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setModInfo(ModInfo modInfo) {
|
||||||
|
this.modInfo = modInfo;
|
||||||
|
server.getEventManager().fireAndForget(new PlayerModInfoEvent(this, this.modInfo));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InetSocketAddress getRemoteAddress() {
|
public InetSocketAddress getRemoteAddress() {
|
||||||
return (InetSocketAddress) connection.getRemoteAddress();
|
return (InetSocketAddress) connection.getRemoteAddress();
|
||||||
|
@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||||
import com.velocitypowered.api.proxy.InboundConnection;
|
import com.velocitypowered.api.proxy.InboundConnection;
|
||||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||||
|
import com.velocitypowered.api.util.ModInfo;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
@ -42,7 +43,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
|||||||
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()),
|
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()),
|
||||||
configuration.getMotdComponent(),
|
configuration.getMotdComponent(),
|
||||||
configuration.getFavicon().orElse(null),
|
configuration.getFavicon().orElse(null),
|
||||||
configuration.isAnnounceForge() ? ServerPing.ModInfo.DEFAULT : null
|
configuration.isAnnounceForge() ? ModInfo.DEFAULT : null
|
||||||
);
|
);
|
||||||
|
|
||||||
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
|
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
|
||||||
|
@ -2,6 +2,8 @@ package com.velocitypowered.proxy.protocol.util;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.velocitypowered.api.util.ModInfo;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||||
@ -76,4 +78,23 @@ public class PluginMessageUtil {
|
|||||||
newMsg.setData(rewrittenData);
|
newMsg.setData(rewrittenData);
|
||||||
return newMsg;
|
return newMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<ModInfo.Mod> readModList(PluginMessage message) {
|
||||||
|
List<ModInfo.Mod> mods = Lists.newArrayList();
|
||||||
|
|
||||||
|
ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getData());
|
||||||
|
byte discriminator = byteBuf.readByte();
|
||||||
|
|
||||||
|
if (discriminator == 2) {
|
||||||
|
int modCount = ProtocolUtils.readVarInt(byteBuf);
|
||||||
|
|
||||||
|
for (int index = 0; index < modCount; index++) {
|
||||||
|
String id = ProtocolUtils.readString(byteBuf);
|
||||||
|
String version = ProtocolUtils.readString(byteBuf);
|
||||||
|
mods.add(new ModInfo.Mod(id, version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImmutableList.copyOf(mods);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren