1
\$\begingroup\$

I wrote two plugins with the Spigot-API. It would be very nice if you could give me any advice so I can improve my code.

https://github.com/ReflexArtZ/NoBots LOC: ~ 800-1000

Main:

package de.emeraldmc.nobots;

import de.emeraldmc.nobots.listeners.LoginListener;
import de.emeraldmc.nobots.listeners.QuitListener;
import de.emeraldmc.nobots.utils.*;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;

public class Main extends JavaPlugin {

    private static Main instance;
    private Config config;
    private boolean attacked; //ToDo
    private boolean updating;
    private HashMap<Player, GoodPercentage> goodPlayers = new HashMap<>();

    @Override
    public void onLoad() {
        instance = this;
    }

    @Override
    public void onEnable() {
        config = new Config();

        registerEvents();

        ConfigManager.readConfig();
        ConfigManager.setConfig();

        MySQL.createTables();

        BlackList.updateBlackList();
    }

    @Override
    public void onDisable() {
    }

    private void registerEvents() {
        Bukkit.getPluginManager().registerEvents(new LoginListener(), this);
        Bukkit.getPluginManager().registerEvents(new QuitListener(), this);
    }

    public static Main getInstance() {
        return instance;
    }
    public Config getConfiguration() {
        return config;
    }
    public void setAttacked(boolean attacked) {
        this.attacked = attacked;
    }

    public boolean isUpdating() {
        return updating;
    }

    public void setUpdating(boolean updating) {
        this.updating = updating;
    }

    public HashMap<Player, GoodPercentage> getGoodPlayers() {
        return goodPlayers;
    }
}

AutoWhiteList:

package de.emeraldmc.nobots.utils;

import de.emeraldmc.nobots.Main;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;

public class AutoWhiteList {
    private boolean started;
    private int taskId;

    /**
     * @deprecated scheduleAsyncRepeatingTask
     */
    public void start() {
        if (started) stop();
        taskId = Bukkit.getScheduler().scheduleAsyncRepeatingTask(Main.getInstance(), new Runnable() {
            @Override
            public void run() {
                for (Player p : Bukkit.getOnlinePlayers()) {
                    if (!WhiteList.isUUIDWhiteListed(p.getUniqueId())) {
                        if (Main.getInstance().getGoodPlayers().get(p).calcPercentage() > 50) {
                            WhiteList.addUUID(p.getUniqueId());
                            for (Player op : Bukkit.getOnlinePlayers()) {
                                if (op.isOp()) {
                                    ChatAPI.sendMessage(op, "Auto Whitelisted: "+p.getName());
                                }
                            }
                        }
                    }
                }
            }
        }, 0l, 120l); // 6 seconds
    }
    public void stop() {
        Bukkit.getServer().getScheduler().cancelTask(taskId);
        started = false;
        Debug.print("Stopped AutoWhiteList task ("+taskId+")");
    }
}

BlackList:

package de.emeraldmc.nobots.utils;

import de.emeraldmc.nobots.Main;
import org.bukkit.Bukkit;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class BlackList {
    public static void addIp(String address) {
        if (address.length() > 15) return;
        if (BlackList.isIpBlackListed(address)) return;
        try {
            PreparedStatement preparedStatement = MySQL.getInstance().prepareStatement("INSERT INTO NoBots_BlackList (IP) VALUES (?)");
            preparedStatement.setString(1, address);
            preparedStatement.executeUpdate();
            Debug.print("&7Added &c"+address+" &7to the BlackList");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    public static void removeIp(String address) {
        if (address.length() > 15) return;
        if (!BlackList.isIpBlackListed(address)) return;
        try {
            PreparedStatement preparedStatement = MySQL.getInstance().prepareStatement("DELETE FROM NoBots_BlackList WHERE IP = ?");
            preparedStatement.setString(1, address);
            preparedStatement.executeUpdate();
            Debug.print("Removed "+address+" from the BlackList");
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }
    public static boolean isIpBlackListed(String address) {
        try {
            PreparedStatement preparedStatement = MySQL.getInstance().prepareStatement("SELECT * FROM NoBots_BlackList WHERE IP = ?");
            preparedStatement.setString(1, address);
            ResultSet rs = preparedStatement.executeQuery();
            return rs.next();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }
    public static void updateBlackList() {
        Bukkit.getServer().getScheduler().runTaskAsynchronously(Main.getInstance(), new Runnable() {
            @Override
            public void run() {
                try {
                    Main.getInstance().setUpdating(true);
                    URL url = new URL("https://myip.ms/files/blacklist/csf/latest_blacklist.txt");
                    BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
                    String address;
                    int z = 0;
                    while ((address = in.readLine()) != null) {
                        if (address.contains(":") || BlackList.isIpBlackListed(address) || address.startsWith("#")) {
                            continue;
                        }
                        BlackList.addIp(address);
                        z++;
                    }
                    in.close();
                    Bukkit.getConsoleSender().sendMessage("§aBlackList updated"+"\n"+"\t"+"-> Added "+z+" bad addresses!");
                    Main.getInstance().setUpdating(false);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

}

Config:

package de.emeraldmc.nobots.utils;

import org.bukkit.ChatColor;
import org.bukkit.configuration.file.FileConfiguration;

import java.util.ArrayList;
import java.util.UUID;

/**
 * @author Patrick Sommer
 */
public class Config {
    private static FileConfiguration cfg = ConfigManager.getConfigFileConfiguration();

    private boolean debug;

    private boolean maxIp;
    private int maxIpLimit;
    private String maxIpKickMessage;

    private int minimumAttackers;

    private String updatingKickMessage;

    private boolean enableCountryWhiteList;
    private ArrayList<String> whiteListedCountries;
    private String whiteListedCountriesKickMessage;

    private ArrayList<UUID> whiteListedPlayers;

    private String dbHost;
    private int dbPort;
    private String database;
    private String dbUser;
    private String dbPassword;

    public Config() {
        debug = cfg.getBoolean("Config.debug");

        maxIp = cfg.getBoolean("Config.MaxIp.enabled");
        maxIpLimit = cfg.getInt("Config.MaxIp.limit");
        maxIpKickMessage = cfg.getString("Config.MaxIp.kickMessage");

        minimumAttackers = cfg.getInt("Config.Attacked.minimumAttackers");

        updatingKickMessage = cfg.getString("Config.updatingKickMessage");

        enableCountryWhiteList = cfg.getBoolean("Config.WhiteListedCountries.enabled");
        whiteListedCountries = new ArrayList<>(cfg.getStringList("Config.WhiteListedCountries.countries"));
        whiteListedCountriesKickMessage = cfg.getString("Config.WhiteListedCountries.kickMessage");

        dbHost = cfg.getString("Config.MySQL.host");
        dbPort = cfg.getInt("Config.MySQL.port");
        database = cfg.getString("Config.MySQL.database");
        dbUser = cfg.getString("Config.MySQL.user");
        dbPassword = cfg.getString("Config.MySQL.password");
    }



    public static FileConfiguration getCfg() {
        return cfg;
    }

    public boolean isDebug() {
        return debug;
    }

    public boolean isMaxIp() {
        return maxIp;
    }

    public int getMaxIpLimit() {
        return maxIpLimit;
    }

    public String getMaxIpKickMessage() {
        return maxIpKickMessage;
    }


    public int getMinimumAttackers() {
        return minimumAttackers;
    }


    public String getDbHost() {
        return dbHost;
    }

    public int getDbPort() {
        return dbPort;
    }

    public String getDatabase() {
        return database;
    }

    public String getDbUser() {
        return dbUser;
    }

    public String getDbPassword() {
        return dbPassword;
    }

    public ArrayList<String> getWhiteListedCountries() {
        return whiteListedCountries;
    }

    public boolean isEnableCountryWhiteList() {
        return enableCountryWhiteList;
    }

    public String getWhiteListedCountriesKickMessage() {
        return whiteListedCountriesKickMessage;
    }

    public String getUpdatingKickMessage() {
        return updatingKickMessage;
    }
}

ConfigManager:

package de.emeraldmc.nobots.utils;

import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

/**
 * Creates, loads and fills the Config file so the user can configure it
 * @author Patrick Sommer
 */
public class ConfigManager {
    public static File getConfigFile() {
        return new File("plugins/NoBots", "config.yml");
    }
    public static FileConfiguration getConfigFileConfiguration() {
        return YamlConfiguration.loadConfiguration(getConfigFile());
    }

    /**
     * Set the standard configuration
     */
    public static void setConfig() {
        FileConfiguration cfg = getConfigFileConfiguration();
        cfg.options().copyDefaults(true);
        cfg.addDefault("Config.debug", false);
        cfg.addDefault("Config.MaxIp.enabled", true);
        cfg.addDefault("Config.MaxIp.limit", 2);
        cfg.addDefault("Config.MaxIp.kickMessage", "&cYou are kicked!");
        cfg.addDefault("Config.Attacked.minimumAttackers", 5);
        ArrayList<String> whiteListedCountries = new ArrayList<>();
        whiteListedCountries.add("Austria");
        whiteListedCountries.add("Germany");
        whiteListedCountries.add("United States");
        cfg.addDefault("Config.WhiteListedCountries.enabled", true);
        cfg.addDefault("Config.WhiteListedCountries.countries", whiteListedCountries);
        cfg.addDefault("Config.WhiteListedCountries.kickMessage", "&cYou are not in a whitelisted country!");
        cfg.addDefault("Config.updatingKickMessage", "&cPlease come back in a minute, the Blacklist is updating!");
        cfg.addDefault("Config.MySQL.host", "127.0.0.1");
        cfg.addDefault("Config.MySQL.port", 3306);
        cfg.addDefault("Config.MySQL.database", "NoBots");
        cfg.addDefault("Config.MySQL.user", "root");
        cfg.addDefault("Config.MySQL.password", "toor");
        try {
            cfg.save(getConfigFile());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Read the configuration
     */
    public static void readConfig() {
        FileConfiguration cfg = getConfigFileConfiguration();
    }
}

GoodPercentage:

package de.emeraldmc.nobots.utils;

public class GoodPercentage {
    private int factors;
    private int applicables;
    private int percentage;

    public GoodPercentage(int factors) {
        setFactors(factors);
        applicables = 0;
        calcPercentage();
    }


    public int calcPercentage() {
        percentage = (factors/applicables)*100;
        return percentage;
    }
    public void addApplicables() {
        if (applicables >= factors) return;
        applicables++;
        calcPercentage();
    }
    public void setFactors(int factors) {
        if (factors < 1) return;
        this.factors = factors;
    }

    public int getFactors() {
        return factors;
    }

    public int getApplicables() {
        return applicables;
    }

    public int getPercentage() {
        return percentage;
    }
}

MySQL:

package de.emeraldmc.nobots.utils;

import de.emeraldmc.nobots.Main;
import org.bukkit.Bukkit;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class MySQL {
    private static Connection connection = null;

    private static String host = Main.getInstance().getConfiguration().getDbHost();

    private static int port = Main.getInstance().getConfiguration().getDbPort();

    private static String database = Main.getInstance().getConfiguration().getDatabase();

    private static String user = Main.getInstance().getConfiguration().getDbUser();

    private static String password = Main.getInstance().getConfiguration().getDbPassword();

    private MySQL() {
        try {
            // load database drivers for ODBC interfaces
            Class.forName("com.mysql.jdbc.Driver");

            // connect to ODBC database
            connection = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database + "?" + "user=" + user + "&" + "password=" + password);
        } catch (ClassNotFoundException e) {
            Bukkit.getConsoleSender().sendMessage("§c[-] §7Driver not found!");
            e.printStackTrace();
        } catch (SQLException e) {
            Bukkit.getConsoleSender().sendMessage("§c[-] §7Could not connect to database!");
            e.printStackTrace();
        }
    }

    public static void closeConnection() {
        try {
            connection.close();
            Debug.print("Cloes MySQL connection");
        } catch (SQLException e) {
            e.printStackTrace();
            connection = null;
        }
    }

    public static void createTables() {
        Connection conn = getInstance();
        try {
            // ToDo: conn.prepareStatement("CREATE TABLE IF NOT EXISTS NoBots_WhiteList (UUID VARCHAR (36))").execute();
            conn.prepareStatement("CREATE  TABLE IF NOT EXISTS NoBots_BlackList (IP VARCHAR (15))").execute(); // ToDo: Add IPv6 support
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    public static Connection getInstance() {
        if (connection == null) {
            new MySQL();
        }
        return connection;
    }
}

ProxyCheck:

package de.emeraldmc.nobots.utils;

import de.emeraldmc.nobots.Main;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Pattern;

public class ProxyCheck {
    public static boolean isProxy(String web, String result) {
        try {
            URL url = new URL(web);
            BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
            String inLine;
            String lineSum = new String();
            while ((inLine = in.readLine()) != null) {
                lineSum += inLine;
            }
            in.close();

            return lineSum.contains(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
    public static String getCountry(String address) {
        try {
            URL url = new URL("http://geoip.nekudo.com/api/"+address);
            BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
            String inLine;
            String lineSum = new String();
            while ((inLine = in.readLine()) != null) {
                lineSum += inLine;
            }
            in.close();
            if (lineSum.contains("error")) {
                return "Unavailable";
            }
            String[] arr = lineSum.split(",");
            String c = arr[1];
            c = c.replaceAll(Pattern.quote("{"), "");
            c = c.replaceAll(String.valueOf('"'), "");
            c = c.replaceAll("country:name:", "");

            return c;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

WhiteList:

package de.emeraldmc.nobots.utils;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;

public class WhiteList {
    public static void addUUID(UUID uuid) {
        if (WhiteList.isUUIDWhiteListed(uuid)) return;
        try {
            PreparedStatement preparedStatement = MySQL.getInstance().prepareStatement("INSERT INTO NoBots_WhiteList (UUID) VALUES (?)");
            preparedStatement.setString(1, uuid.toString());
            preparedStatement.executeUpdate();
            Debug.print("Added "+uuid+" to the WhiteList");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    public static void removeUUID(UUID uuid) {
        if (!WhiteList.isUUIDWhiteListed(uuid)) return;
        try {
            PreparedStatement preparedStatement = MySQL.getInstance().prepareStatement("DELETE FROM NoBots_WhiteList WHERE UUID = ?");
            preparedStatement.setString(1, uuid.toString());
            preparedStatement.executeUpdate();
            Debug.print("Removed "+uuid+" from the WhiteList");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    public static boolean isUUIDWhiteListed(UUID uuid) {
        try {
            PreparedStatement preparedStatement = MySQL.getInstance().prepareStatement("SELECT * FROM NoBots_WhiteList WHERE UUID = ?");
            preparedStatement.setString(1, uuid.toString());
            ResultSet rs = preparedStatement.executeQuery();
            return rs.next();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }
}

AchievementAwardedEvent:

package de.emeraldmc.nobots.listeners;

import de.emeraldmc.nobots.Main;
import de.emeraldmc.nobots.utils.WhiteList;
import org.bukkit.Achievement;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerAchievementAwardedEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;

public class AchievementAwardedEvent {

    @EventHandler
    public void onAchievementAwarded(PlayerAchievementAwardedEvent e) {
        if (e.getAchievement() != Achievement.OPEN_INVENTORY && !WhiteList.isUUIDWhiteListed(e.getPlayer().getUniqueId())) {
            Main.getInstance().getGoodPlayers().get(e.getPlayer()).addApplicables();
        }
    }
}

BlockBreakListener:

package de.emeraldmc.nobots.listeners;

import de.emeraldmc.nobots.Main;
import de.emeraldmc.nobots.utils.WhiteList;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.player.PlayerAchievementAwardedEvent;

import java.util.ArrayList;

public class BlockBreakListener implements Listener {

    private static ArrayList<Player> blockBreaked = new ArrayList<>();

    @EventHandler
    public void onBlockBreak(BlockBreakEvent e) {
        if (!blockBreaked.contains(e.getPlayer()) && !WhiteList.isUUIDWhiteListed(e.getPlayer().getUniqueId())) {
            Main.getInstance().getGoodPlayers().get(e.getPlayer()).addApplicables();
            blockBreaked.add(e.getPlayer());
        }
    }
}

CommandPreprocessListener:

package de.emeraldmc.nobots.listeners;

import de.emeraldmc.nobots.Main;
import de.emeraldmc.nobots.utils.WhiteList;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;

import java.util.ArrayList;

public class CommandPreprocessListener implements Listener {

    private static ArrayList<Player> commandPlayers = new ArrayList<>();

    @EventHandler
    public void onCommandPreprocess(PlayerCommandPreprocessEvent e) {
        if (!commandPlayers.contains(e.getPlayer()) && (!e.getMessage().contains("register") || !e.getMessage().contains("login")) && !WhiteList.isUUIDWhiteListed(e.getPlayer().getUniqueId())) {
            Main.getInstance().getGoodPlayers().get(e.getPlayer()).addApplicables();
        }
    }
}

JoinListener:

package de.emeraldmc.nobots.listeners;

import de.emeraldmc.nobots.Main;
import de.emeraldmc.nobots.utils.GoodPercentage;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;

public class JoinListener implements Listener {
    @EventHandler
    public void onJoin(PlayerJoinEvent e) {
        Player p = e.getPlayer();
        GoodPercentage goodPercentage = new GoodPercentage(4); // 2 for AchievementAwards, 1 for BlockBreak, 1 for Command
        Main.getInstance().getGoodPlayers().put(p, goodPercentage);
    }
}

LoginListener:

package de.emeraldmc.nobots.listeners;

import de.emeraldmc.nobots.Main;
import de.emeraldmc.nobots.utils.*;
import org.bukkit.ChatColor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import java.net.InetAddress;
import java.util.ArrayList;

public class LoginListener implements Listener {

    // List of connected addresses
    public static ArrayList<String> addresses = new ArrayList<>(); //Format: 127.0.0.1

    @EventHandler
    public void onLogin(AsyncPlayerPreLoginEvent e) {
        String player = e.getName();
        InetAddress address = e.getAddress();
        String strAddress = address.toString().replace("/", "");
        String country = ProxyCheck.getCountry(strAddress);
        Debug.print("Player: "+player+"\n"+"\t"+"-> Address: "+strAddress+"\n"+"\t"+"-> Country: "+country);
        if (strAddress.equals("127.0.0.1")) {
            return;
        }
        if (WhiteList.isUUIDWhiteListed(e.getUniqueId())) {
            return;
        }

        if (Main.getInstance().isUpdating()) {
            e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, ChatAPI.translateColor(Main.getInstance().getConfiguration().getUpdatingKickMessage()));
            return;
        }
        if (limitReached(strAddress)) {
            Debug.print("\t"+"&cBlocked!");
            e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, ChatAPI.translateColor(Main.getInstance().getConfiguration().getMaxIpKickMessage()));
            return;
        }
        if (BlackList.isIpBlackListed(strAddress)) {
            e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "Bots are not allowed!");
            return;
        }
        if (!isInAllowedCountry(strAddress, country)) {
            e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, ChatAPI.translateColor(Main.getInstance().getConfiguration().getWhiteListedCountriesKickMessage()));
            return;
        }
        if (ProxyCheck.isProxy("http://api.stopforumspam.org/api?ip=" + strAddress, "<appears>yes</appears>")) {
            Debug.print("Bot found!: "+strAddress);
            BlackList.addIp(strAddress);
            e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "Bots are not allowed!");
            return;
        }
        if (ProxyCheck.isProxy("http://legacy.iphub.info/api.php?ip="+ strAddress +"&showtype=4", "\"proxy\":1")) {
            Debug.print("Bot found!: "+strAddress);
            BlackList.addIp(strAddress);
            e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "Bots are not allowed!");
            return;
        }

        addresses.add(strAddress);
    }

    private boolean limitReached(String address) {
        int limit = Main.getInstance().getConfiguration().getMaxIpLimit();
        int i = 0;
        for (String strAddress : addresses) {
            if (strAddress.equals(address)) {
                i++;
            }
        }
        if (i >= Main.getInstance().getConfiguration().getMinimumAttackers()) {
            Main.getInstance().setAttacked(true);
        }
        return i > limit;
    }
    private boolean isInAllowedCountry(String address, String country) {
        if (country == null) return false;
        for (String c : Main.getInstance().getConfiguration().getWhiteListedCountries()) {
            if (c.equalsIgnoreCase(country)) {
                return true;
            }
        }
        return false;
    }

}

QuitListener:

package de.emeraldmc.nobots.listeners;

import de.emeraldmc.nobots.utils.Debug;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;

public class QuitListener implements Listener {

    @EventHandler
    public void onQuit(PlayerQuitEvent e) {
        String address = e.getPlayer().getAddress().getAddress().toString().replace("/", "");
        LoginListener.addresses.remove(address);
    }
}
\$\endgroup\$
  • \$\begingroup\$ I think it is 1 program per question. Please choose one, and post another question. \$\endgroup\$ – Rakete1111 Mar 31 '17 at 17:39
  • \$\begingroup\$ Welcome to StackExchange Code Review! Please review How do I ask a good Question? Specifically, Be sure to embed the code you want reviewed in the question itself; you can leave supporting, but non-essential, code in links to other sites. \$\endgroup\$ – Stephen Rauch Mar 31 '17 at 17:50
  • \$\begingroup\$ I would really recommend that you edit your question and at least give the most important classes, because there are some improvements that can be done here. \$\endgroup\$ – Simon Forsberg Mar 31 '17 at 20:59
  • \$\begingroup\$ @SimonForsberg thanks for your advice! I have done this now! \$\endgroup\$ – Patrick Mar 31 '17 at 21:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.