forked from TuxCoding/FastLogin
Remove Importer to prepare for code refactor
This commit is contained in:
@ -44,7 +44,7 @@ So they don't need to enter passwords. This is also called auto login (auto-logi
|
||||
* Run Spigot and/or BungeeCord/Waterfall in offline mode (see server.properties or config.yml)
|
||||
* An auth plugin. Supported plugins
|
||||
|
||||
#### Bukkit/Spigot/PaperSpigot
|
||||
#### Bukkit/Spigot/Paper
|
||||
|
||||
* [AuthMe (both 5.X and 3.X)](https://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
|
||||
* [xAuth](https://dev.bukkit.org/bukkit-plugins/xauth/)
|
||||
@ -67,7 +67,7 @@ https://www.spigotmc.org/resources/fastlogin.14153/history
|
||||
|
||||
### How to install
|
||||
|
||||
#### Bukkit/Spigot/PaperSpigot
|
||||
#### Bukkit/Spigot/Paper
|
||||
|
||||
1. Download and install ProtocolLib
|
||||
2. Download and install FastLogin
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
|
||||
import com.github.games647.fastlogin.bukkit.commands.ImportCommand;
|
||||
import com.github.games647.fastlogin.bukkit.commands.PremiumCommand;
|
||||
import com.github.games647.fastlogin.bukkit.listener.BukkitJoinListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.BungeeCordListener;
|
||||
@ -102,7 +101,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
//register commands using a unique name
|
||||
getCommand("premium").setExecutor(new PremiumCommand(this));
|
||||
getCommand("cracked").setExecutor(new CrackedCommand(this));
|
||||
getCommand("import-auth").setExecutor(new ImportCommand(core));
|
||||
|
||||
if (getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) {
|
||||
//prevents NoClassDef errors if it's not available
|
||||
|
@ -1,85 +0,0 @@
|
||||
package com.github.games647.fastlogin.bukkit.commands;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.importer.ImportPlugin;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class ImportCommand implements CommandExecutor {
|
||||
|
||||
private final FastLoginCore<Player, CommandSender, FastLoginBukkit> core;
|
||||
|
||||
public ImportCommand(FastLoginCore<Player, CommandSender, FastLoginBukkit> core) {
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length < 2) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You need to specify the import plugin and database type");
|
||||
return true;
|
||||
}
|
||||
|
||||
ImportPlugin importPlugin;
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "autoin":
|
||||
importPlugin = ImportPlugin.AUTO_IN;
|
||||
break;
|
||||
case "bpa":
|
||||
importPlugin = ImportPlugin.BPA;
|
||||
break;
|
||||
case "eldzi":
|
||||
importPlugin = ImportPlugin.ELDZI;
|
||||
break;
|
||||
default:
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Unknown auto login plugin");
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean sqlite;
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "sqlite":
|
||||
sqlite = true;
|
||||
break;
|
||||
case "mysql":
|
||||
sqlite = false;
|
||||
break;
|
||||
default:
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Unknown storage type to import from. Either SQLite or MySQL");
|
||||
return true;
|
||||
}
|
||||
|
||||
String host = "";
|
||||
String database = "";
|
||||
String username = "";
|
||||
String password = "";
|
||||
if (!sqlite) {
|
||||
if (args.length <= 5) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "If importing from MySQL, you need to specify host database "
|
||||
+ "and username passowrd too");
|
||||
return true;
|
||||
}
|
||||
|
||||
host = args[2];
|
||||
database = args[3];
|
||||
username = args[4];
|
||||
password = args[5];
|
||||
}
|
||||
|
||||
AuthStorage storage = core.getStorage();
|
||||
boolean success = core.importDatabase(importPlugin, true, storage, host, database, username, password);
|
||||
if (success) {
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Successful imported the data");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Failed to import the data. Check out the logs");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -170,7 +170,6 @@ public class VerifyResponseTask implements Runnable {
|
||||
|
||||
PacketContainer kickPacket = protocolManager.createPacket(PacketType.Login.Server.DISCONNECT);
|
||||
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
|
||||
|
||||
try {
|
||||
//send kick packet at login state
|
||||
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
|
||||
|
@ -33,11 +33,6 @@ commands:
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.cracked
|
||||
|
||||
import-auth:
|
||||
description: 'Imports the auth data from another auto login'
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.import
|
||||
|
||||
permissions:
|
||||
${project.artifactId}.command.premium:
|
||||
description: 'Label themselves as premium'
|
||||
|
@ -49,9 +49,6 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
|
||||
getProxy().getPluginManager().registerListener(this, new PlayerConnectionListener(this));
|
||||
getProxy().getPluginManager().registerListener(this, new PluginMessageListener(this));
|
||||
|
||||
//bungee only commands
|
||||
getProxy().getPluginManager().registerCommand(this, new ImportCommand(this));
|
||||
|
||||
//this is required to listen to messages from the server
|
||||
getProxy().registerChannel(getDescription().getName());
|
||||
|
||||
|
@ -1,93 +0,0 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.importer.ImportPlugin;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class ImportCommand extends Command {
|
||||
|
||||
private final FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core;
|
||||
|
||||
public ImportCommand(FastLoginBungee plugin) {
|
||||
super("import-db", plugin.getDescription().getName().toLowerCase() + ".import");
|
||||
|
||||
this.core = plugin.getCore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (args.length < 2) {
|
||||
String message = ChatColor.DARK_RED + "You need to specify the import plugin and database type";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
ImportPlugin importPlugin;
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "autoin":
|
||||
importPlugin = ImportPlugin.AUTO_IN;
|
||||
break;
|
||||
case "bpa":
|
||||
importPlugin = ImportPlugin.BPA;
|
||||
break;
|
||||
case "eldzi":
|
||||
importPlugin = ImportPlugin.ELDZI;
|
||||
break;
|
||||
default:
|
||||
String message = ChatColor.DARK_RED + "Unknown auto login plugin";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean sqlite;
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "sqlite":
|
||||
sqlite = true;
|
||||
break;
|
||||
case "mysql":
|
||||
sqlite = false;
|
||||
break;
|
||||
default:
|
||||
String message = ChatColor.DARK_RED + "Unknown storage type to import from. Either SQLite or MySQL";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
String host = "";
|
||||
String database = "";
|
||||
String username = "";
|
||||
String password = "";
|
||||
if (!sqlite) {
|
||||
if (args.length <= 5) {
|
||||
String message = ChatColor.DARK_RED + "If importing from MySQL, you need to specify host database "
|
||||
+ "and username passowrd too";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
host = args[2];
|
||||
database = args[3];
|
||||
username = args[4];
|
||||
password = args[5];
|
||||
}
|
||||
|
||||
AuthStorage storage = core.getStorage();
|
||||
boolean success = core.importDatabase(importPlugin, true, storage, host, database, username, password);
|
||||
if (success) {
|
||||
sender.sendMessage(convertFromLegacy(ChatColor.DARK_GREEN + "Successful imported the data"));
|
||||
} else {
|
||||
sender.sendMessage(convertFromLegacy(ChatColor.DARK_RED + "Failed to import the data. Check out the logs"));
|
||||
}
|
||||
}
|
||||
|
||||
private BaseComponent[] convertFromLegacy(String message) {
|
||||
return TextComponent.fromLegacyText(message);
|
||||
}
|
||||
}
|
@ -64,7 +64,7 @@ public class ForceLoginTask extends ForceLoginMangement<ProxiedPlayer, CommandSe
|
||||
@Override
|
||||
public void onForceActionSuccess(LoginSession session) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
//subchannel name
|
||||
//sub channel name
|
||||
if (session.needsRegistration()) {
|
||||
dataOutput.writeUTF("AUTO_REGISTER");
|
||||
} else {
|
||||
|
@ -1,73 +0,0 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class AutoInImporter extends Importer {
|
||||
|
||||
private static final String PLUGIN_NAME = "AutoIn";
|
||||
|
||||
private static final String SQLITE_FILE = "plugins/" + PLUGIN_NAME + "/AutoIn_PlayerOptions.db";
|
||||
|
||||
private static final String USER_TABLE = "nicknames";
|
||||
private static final String UUID_TABLE = "uuids";
|
||||
private static final String SESSION_TABLE = "sessions";
|
||||
|
||||
public static String getSQLitePath() {
|
||||
return SQLITE_FILE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException {
|
||||
Statement stmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
stmt = source.createStatement();
|
||||
resultSet = stmt.executeQuery("SELECT name, protection, premium, puuid FROM " + USER_TABLE
|
||||
+ " LEFT JOIN " + " ("
|
||||
/* Prevent duplicates */
|
||||
+ "SELECT * FROM " + UUID_TABLE + " GROUP BY nickname_id"
|
||||
+ ") uuids"
|
||||
+ " ON " + USER_TABLE + ".id = uuids.nickname_id");
|
||||
|
||||
int rows = 0;
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString(1);
|
||||
boolean protection = resultSet.getBoolean(2);
|
||||
/* Enable premium authentication only for those who want to be auto logged in,
|
||||
so they have their cracked protection disabled */
|
||||
boolean premium = !protection && resultSet.getBoolean(3);
|
||||
String puuid = resultSet.getString(4);
|
||||
|
||||
/* FastLogin will also make lookups on the uuid column for name changes
|
||||
the old 1.6.2 version won't check if those user have premium enabled
|
||||
|
||||
so it could happen that a premium could steal the account if we don't do this
|
||||
|
||||
It seems the uuid is saved on autoin too if the player is cracked */
|
||||
PlayerProfile profile;
|
||||
if (premium) {
|
||||
profile = new PlayerProfile(UUID.fromString(puuid), name, premium, "");
|
||||
} else {
|
||||
profile = new PlayerProfile(null, name, premium, "");
|
||||
}
|
||||
|
||||
storage.save(profile);
|
||||
rows++;
|
||||
}
|
||||
|
||||
return rows;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class BPAImporter extends Importer {
|
||||
|
||||
private static final String DEFAULT_TABLE_NAME = "users";
|
||||
|
||||
@Override
|
||||
public int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException {
|
||||
Statement stmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
stmt = source.createStatement();
|
||||
resultSet = stmt.executeQuery("SELECT "
|
||||
+ "nick, "
|
||||
+ "checked, "
|
||||
+ "lastIP, "
|
||||
+ "FROM_UNIXTIME(lastJoined * 0.001) AS LastLogin "
|
||||
+ "FROM " + DEFAULT_TABLE_NAME);
|
||||
|
||||
int rows = 0;
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString(1);
|
||||
boolean premium = resultSet.getBoolean(2);
|
||||
String lastIP = resultSet.getString(3);
|
||||
Timestamp lastLogin = resultSet.getTimestamp(4);
|
||||
|
||||
//uuid doesn't exist here
|
||||
PlayerProfile profile = new PlayerProfile(null, name, premium, lastIP);
|
||||
storage.save(profile);
|
||||
rows++;
|
||||
}
|
||||
|
||||
return rows;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class ElDziAuthImporter extends Importer {
|
||||
|
||||
private static final String TABLE_NAME = "accounts";
|
||||
|
||||
@Override
|
||||
public int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException {
|
||||
Statement stmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
stmt = source.createStatement();
|
||||
resultSet = stmt.executeQuery("SELECT "
|
||||
+ "nick, "
|
||||
+ "premium, "
|
||||
+ "lastIP, "
|
||||
+ "FROM_UNIXTIME(lastPlayed * 0.001) AS LastLogin "
|
||||
+ "FROM " + TABLE_NAME);
|
||||
|
||||
int rows = 0;
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString(1);
|
||||
boolean premium = resultSet.getBoolean(2);
|
||||
String lastIP = resultSet.getString(3);
|
||||
Timestamp lastLogin = resultSet.getTimestamp(4);
|
||||
|
||||
String uuid = resultSet.getString(5);
|
||||
|
||||
PlayerProfile profile;
|
||||
if (premium) {
|
||||
profile = new PlayerProfile(UUID.fromString(uuid), name, premium, lastIP);
|
||||
} else {
|
||||
profile = new PlayerProfile(null, name, premium, "");
|
||||
}
|
||||
|
||||
storage.save(profile);
|
||||
rows++;
|
||||
}
|
||||
|
||||
return rows;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
public enum ImportPlugin {
|
||||
|
||||
AUTO_IN(AutoInImporter.class),
|
||||
|
||||
BPA(BPAImporter.class),
|
||||
|
||||
ELDZI(ElDziAuthImporter.class);
|
||||
|
||||
private final Class<? extends Importer> importerClass;
|
||||
|
||||
ImportPlugin(Class<? extends Importer> importer) {
|
||||
this.importerClass = importer;
|
||||
}
|
||||
|
||||
public Class<? extends Importer> getImporter() {
|
||||
return importerClass;
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public abstract class Importer {
|
||||
|
||||
public abstract int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException;
|
||||
|
||||
protected void closeQuietly(AutoCloseable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception ignore) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,9 +6,6 @@ import com.github.games647.fastlogin.core.SharedConfig;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.importer.AutoInImporter;
|
||||
import com.github.games647.fastlogin.core.importer.ImportPlugin;
|
||||
import com.github.games647.fastlogin.core.importer.Importer;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
@ -19,9 +16,6 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -180,46 +174,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean importDatabase(ImportPlugin importPlugin, boolean sqlite, AuthStorage storage, String host, String database
|
||||
, String username, String pass) {
|
||||
if (sqlite && (importPlugin == ImportPlugin.BPA || importPlugin == ImportPlugin.ELDZI)) {
|
||||
throw new IllegalArgumentException("These plugins doesn't support flat file databases");
|
||||
}
|
||||
|
||||
Importer importer;
|
||||
try {
|
||||
importer = importPlugin.getImporter().newInstance();
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Couldn't not setup importer class", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (sqlite && importPlugin == ImportPlugin.AUTO_IN) {
|
||||
//load SQLite driver
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
|
||||
String jdbcUrl = "jdbc:sqlite:" + AutoInImporter.getSQLitePath();
|
||||
Connection con = DriverManager.getConnection(jdbcUrl);
|
||||
importer.importData(con, storage.getDataSource(), storage);
|
||||
return true;
|
||||
} else {
|
||||
Class.forName("com.mysql.jdbc.Driver");
|
||||
|
||||
String jdbcUrl = "jdbc:mysql://" + host + '/' + database;
|
||||
Connection con = DriverManager.getConnection(jdbcUrl, username, pass);
|
||||
importer.importData(con, storage.getDataSource(), storage);
|
||||
return true;
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Cannot find SQL driver. Do you removed it?", ex);
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Couldn't import data. Aborting...", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public SharedConfig getConfig() {
|
||||
return sharedConfig;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ switchMode: false
|
||||
# will have the same inventory, permissions, ... if they switched to premium authentification from offline/cracked
|
||||
# authentication.
|
||||
#
|
||||
# This feature requires Cauldron, Spigot or a fork of Spigot (PaperSpigot, TacoSpigot)
|
||||
# This feature requires Cauldron, Spigot or a fork of Spigot (Paper)
|
||||
premiumUuid: false
|
||||
|
||||
# This will make an additional check (only for player names which are not in the database) against the mojang servers
|
||||
|
@ -48,7 +48,7 @@ not-premium-other: '&4Player is not in the premium list'
|
||||
# Admin wanted to change the premium of a user that isn't known to the plugin
|
||||
player-unknown: '&4Player not in the database'
|
||||
|
||||
# ========= Bukkit/Spigot/PaperSpigot/TacoSpigot only ================================
|
||||
# ========= Bukkit/Spigot ================
|
||||
|
||||
# The user skipped the authentication, because it was a premium player
|
||||
auto-login: '&2Auto logged in'
|
||||
|
Reference in New Issue
Block a user