1
0
mirror of https://github.com/essentials/Essentials.git synced 2025-08-11 17:15:07 +02:00

Ess2Compat: Convert user files

This commit is contained in:
snowleo
2012-11-03 02:36:17 +01:00
parent 59878a94e4
commit 83fefefb43
6 changed files with 1082 additions and 10 deletions

View File

@@ -1,7 +1,5 @@
package net.ess3.user;
import java.io.File;
import java.io.IOException;
import java.util.*;
import lombok.Delegate;
import net.ess3.api.IEssentials;
@@ -24,7 +22,7 @@ import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
public class UserBase extends AsyncStorageObjectHolder<UserData> implements OfflinePlayer, CommandSender, IStorageObjectHolder<UserData>
public abstract class UserBase extends AsyncStorageObjectHolder<UserData> implements OfflinePlayer, CommandSender, IStorageObjectHolder<UserData>
{
@Delegate
protected final OfflinePlayer offlinePlayer;

View File

@@ -1,16 +1,39 @@
package com.earth2me.essentials;
import java.io.File;
import java.util.Calendar;
import java.util.GregorianCalendar;
import net.ess3.api.IEssentials;
import net.ess3.api.IPlugin;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class Essentials extends JavaPlugin
{
IEssentials ess;
@Override
public void onEnable()
{
Bukkit.getLogger().info("You can remove this compatibility plugin, when all plugins are updated to Essentials-3");
//TODO: Update files to new 3.0 format
//TODO: Move Eco Api here
IPlugin plugin = (IPlugin)getServer().getPluginManager().getPlugin("Essentials-3");
ess = plugin.getEssentials();
updateUserfiles();
}
private void updateUserfiles()
{
File folder = new File(getDataFolder(), "userdata");
if (folder.isDirectory()) {
new UpdateUserFiles(folder, ess);
File folderNew = new File(getDataFolder(), "userdata-"+System.currentTimeMillis());
if (!folderNew.exists()) {
folder.renameTo(folderNew);
}
}
}
}

View File

@@ -14,6 +14,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.ess3.storage.StoredLocation;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Server;
@@ -238,19 +239,14 @@ public class EssentialsConf extends YamlConfiguration
return isSet(path);
}
public Location getLocation(final String path, final Server server) throws Exception
public StoredLocation getLocation(final String path, final Server server) throws Exception
{
final String worldName = getString((path == null ? "" : path + ".") + "world");
if (worldName == null || worldName.isEmpty())
{
return null;
}
final World world = server.getWorld(worldName);
if (world == null)
{
throw new Exception(_("invalidWorld"));
}
return new Location(world,
return new StoredLocation(worldName,
getDouble((path == null ? "" : path + ".") + "x", 0),
getDouble((path == null ? "" : path + ".") + "y", 0),
getDouble((path == null ? "" : path + ".") + "z", 0),

View File

@@ -0,0 +1,171 @@
package com.earth2me.essentials;
import com.google.common.io.PatternFilenameFilter;
import java.io.File;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.ess3.api.IEssentials;
import net.ess3.api.IUser;
import net.ess3.api.InvalidNameException;
import net.ess3.api.NoLoanPermittedException;
import net.ess3.api.UserDoesNotExistException;
import net.ess3.user.User;
import net.ess3.user.UserBase;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
class UpdateUserFiles
{
private static final String BROKENNAME = "BROKENNAME";
private final IEssentials ess;
public UpdateUserFiles(File folder, IEssentials ess)
{
this.ess = ess;
for (String filename : folder.list(new PatternFilenameFilter(".+\\.yml")))
{
File file = new File(folder, filename);
String name = filename.substring(0, filename.length() - 4);
if (name.contains("_"))
{
String origName = name;
name = getRealName(name);
if (name.equals(BROKENNAME))
{
ess.getLogger().warning("Could not convert player " + origName);
continue;
}
}
UserData userData = new UserData(ess, file);
if (userData.isNPC())
{
ess.getEconomy().createNPC(name);
try
{
ess.getEconomy().setMoney(name, userData.getMoney());
}
catch (UserDoesNotExistException ex)
{
ess.getLogger().log(Level.WARNING, "Could not convert player " + name, ex);
}
catch (NoLoanPermittedException ex)
{
ess.getLogger().log(Level.WARNING, "Could not convert player " + name, ex);
}
}
else
{
IUser user = ess.getUserMap().getUser(name);
if (user == null) {
try
{
user = new User(ess.getServer().getOfflinePlayer(name), ess);
}
catch (InvalidNameException ex)
{
ess.getLogger().log(Level.WARNING, "Could not convert player " + name, ex);
continue;
}
}
net.ess3.user.UserData data = user.getData();
data.setPowerToolsEnabled(userData.arePowerToolsEnabled());
data.getBan().setReason(userData.getBanReason());
data.getBan().setTimeout(userData.getBanTimeout());
userData.getHomes();
for (String playerName : userData.getIgnoredPlayers())
{
data.setIgnore(playerName.toLowerCase(Locale.ENGLISH), true);
}
data.setJail(userData.getJail());
data.setTimestamp(net.ess3.user.UserData.TimestampType.JAIL, userData.getJailTimeout());
for (String kit : userData.getKitTimestamps())
{
data.setTimestamp(net.ess3.user.UserData.TimestampType.KIT, kit, userData.getKitTimestamp(kit));
}
data.setTimestamp(net.ess3.user.UserData.TimestampType.LASTHEAL, userData.getLastHealTimestamp());
data.setLastLocation(userData.getLastLocation());
data.setTimestamp(net.ess3.user.UserData.TimestampType.LOGIN, userData.getLastLogin());
data.setIpAddress(userData.getLastLoginAddress());
data.setTimestamp(net.ess3.user.UserData.TimestampType.LOGOUT, userData.getLastLogout());
data.setTimestamp(net.ess3.user.UserData.TimestampType.LASTTELEPORT, userData.getLastTeleportTimestamp());
for (String mail : userData.getMails())
{
data.addMail(mail);
}
data.setMoney(userData.getMoney());
data.setTimestamp(net.ess3.user.UserData.TimestampType.MUTE, userData.getMuteTimeout());
data.setMuted(userData.getMuted());
data.setNickname(userData.getNickname());
for (String string : userData.getPowertools())
{
try
{
int id = Integer.parseInt(string);
Material mat = Material.getMaterial(id);
if (mat != null)
{
data.setPowertool(mat, userData.getPowertool(id));
}
}
catch (NumberFormatException ex)
{
}
}
for (Integer integer : userData.getUnlimited())
{
Material mat = Material.getMaterial(integer);
if (mat != null)
{
data.setUnlimited(mat, true);
}
}
data.setAfk(userData.isAfk());
data.setGodmode(userData.isGodModeEnabled());
data.setJailed(userData.isJailed());
data.setMuted(userData.isMuted());
data.setSocialspy(userData.isSocialSpyEnabled());
data.setTeleportEnabled(userData.isTeleportEnabled());
user.queueSave();
}
}
}
private String getRealName(String name)
{
String realname = getPlayer(name);
return realname == null ? BROKENNAME : realname;
}
private final Map<String, String> players = new HashMap<String, String>();
private String getPlayer(String check)
{
synchronized (players)
{
if (players.isEmpty())
{
File worldFolder = ess.getServer().getWorlds().get(0).getWorldFolder();
File playersFolder = new File(worldFolder, "players");
for (String filename : playersFolder.list(new PatternFilenameFilter(".+\\.dat")))
{
String name = filename.substring(0, filename.length() - 4).toLowerCase(Locale.ENGLISH);
String sanitizedName = Util.sanitizeFileName(name);
String mapName = players.get(sanitizedName);
if (mapName != null && !mapName.equals(name))
{
players.put(sanitizedName, BROKENNAME);
}
else
{
players.put(sanitizedName, name);
}
}
}
}
return players.get(check);
}
}

View File

@@ -0,0 +1,773 @@
package com.earth2me.essentials;
import java.io.File;
import java.util.*;
import net.ess3.api.IEssentials;
import net.ess3.storage.StoredLocation;
import org.bukkit.Location;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration;
import org.bukkit.inventory.ItemStack;
public class UserData
{
protected final transient IEssentials ess;
private EssentialsConf config;
private final File folder;
protected UserData(IEssentials ess, File file)
{
this.ess = ess;
folder = new File(ess.getPlugin().getDataFolder(), "userdata");
if (!folder.exists())
{
folder.mkdirs();
}
config = new EssentialsConf(file);
reloadConfig();
}
public final void reloadConfig()
{
config.load();
money = _getMoney();
unlimited = _getUnlimited();
powertools = _getPowertools();
homes = _getHomes();
lastLocation = _getLastLocation();
lastTeleportTimestamp = _getLastTeleportTimestamp();
lastHealTimestamp = _getLastHealTimestamp();
jail = _getJail();
mails = _getMails();
teleportEnabled = getTeleportEnabled();
ignoredPlayers = getIgnoredPlayers();
godmode = _getGodModeEnabled();
muted = getMuted();
muteTimeout = _getMuteTimeout();
jailed = getJailed();
jailTimeout = _getJailTimeout();
lastLogin = _getLastLogin();
lastLogout = _getLastLogout();
lastLoginAddress = _getLastLoginAddress();
afk = getAfk();
geolocation = _getGeoLocation();
isSocialSpyEnabled = _isSocialSpyEnabled();
isNPC = _isNPC();
arePowerToolsEnabled = _arePowerToolsEnabled();
kitTimestamps = _getKitTimestamps();
nickname = _getNickname();
}
private double money;
private double _getMoney()
{
double money = ess.getSettings().getData().getEconomy().getStartingBalance();
if (config.hasProperty("money"))
{
money = config.getDouble("money", money);
}
if (Math.abs(money) > ess.getSettings().getData().getEconomy().getMaxMoney())
{
money = money < 0 ? -ess.getSettings().getData().getEconomy().getMaxMoney() : ess.getSettings().getData().getEconomy().getMaxMoney();
}
return money;
}
public double getMoney()
{
return money;
}
public void setMoney(double value)
{
money = value;
if (Math.abs(money) > ess.getSettings().getData().getEconomy().getMaxMoney())
{
money = money < 0 ? -ess.getSettings().getData().getEconomy().getMaxMoney() : ess.getSettings().getData().getEconomy().getMaxMoney();
}
config.setProperty("money", value);
config.save();
}
private Map<String, Object> homes;
private Map<String, Object> _getHomes()
{
if (config.isConfigurationSection("homes"))
{
return config.getConfigurationSection("homes").getValues(false);
}
return new HashMap<String, Object>();
}
private String getHomeName(String search)
{
if (Util.isInt(search))
{
try
{
search = getHomes().get(Integer.parseInt(search) - 1);
}
catch (Exception e)
{
}
}
return search;
}
public StoredLocation getHome(String name) throws Exception
{
String search = getHomeName(name);
return config.getLocation("homes." + search, ess.getServer());
}
public List<String> getHomes()
{
return new ArrayList<String>(homes.keySet());
}
public void setHome(String name, Location loc)
{
//Invalid names will corrupt the yaml
name = Util.safeString(name);
homes.put(name, loc);
config.setProperty("homes." + name, loc);
config.save();
}
public void delHome(String name) throws Exception
{
String search = getHomeName(name);
if (!homes.containsKey(search))
{
search = Util.safeString(search);
}
if (homes.containsKey(search))
{
homes.remove(search);
config.removeProperty("homes." + search);
config.save();
}
else
{
throw new Exception("invalidHome");
}
}
public boolean hasHome()
{
if (config.hasProperty("home"))
{
return true;
}
return false;
}
private String nickname;
public String _getNickname()
{
return config.getString("nickname");
}
public String getNickname()
{
return nickname;
}
public void setNickname(String nick)
{
nickname = nick;
config.setProperty("nickname", nick);
config.save();
}
private List<Integer> unlimited;
private List<Integer> _getUnlimited()
{
return config.getIntegerList("unlimited");
}
public List<Integer> getUnlimited()
{
return unlimited;
}
public boolean hasUnlimited(ItemStack stack)
{
return unlimited.contains(stack.getTypeId());
}
public void setUnlimited(ItemStack stack, boolean state)
{
if (unlimited.contains(stack.getTypeId()))
{
unlimited.remove(Integer.valueOf(stack.getTypeId()));
}
if (state)
{
unlimited.add(stack.getTypeId());
}
config.setProperty("unlimited", unlimited);
config.save();
}
private Map<String, Object> powertools;
private Map<String, Object> _getPowertools()
{
if (config.isConfigurationSection("powertools"))
{
return config.getConfigurationSection("powertools").getValues(false);
}
return new HashMap<String, Object>();
}
public Set<String> getPowertools() {
return powertools.keySet();
}
public void clearAllPowertools()
{
powertools.clear();
config.setProperty("powertools", powertools);
config.save();
}
@SuppressWarnings("unchecked")
public List<String> getPowertool(ItemStack stack)
{
return (List<String>)powertools.get("" + stack.getTypeId());
}
@SuppressWarnings("unchecked")
public List<String> getPowertool(int id)
{
return (List<String>)powertools.get("" + id);
}
public void setPowertool(ItemStack stack, List<String> commandList)
{
if (commandList == null || commandList.isEmpty())
{
powertools.remove("" + stack.getTypeId());
}
else
{
powertools.put("" + stack.getTypeId(), commandList);
}
config.setProperty("powertools", powertools);
config.save();
}
public boolean hasPowerTools()
{
return !powertools.isEmpty();
}
private StoredLocation lastLocation;
private StoredLocation _getLastLocation()
{
try
{
return config.getLocation("lastlocation", ess.getServer());
}
catch (Exception e)
{
return null;
}
}
public StoredLocation getLastLocation()
{
return lastLocation;
}
private long lastTeleportTimestamp;
private long _getLastTeleportTimestamp()
{
return config.getLong("timestamps.lastteleport", 0);
}
public long getLastTeleportTimestamp()
{
return lastTeleportTimestamp;
}
public void setLastTeleportTimestamp(long time)
{
lastTeleportTimestamp = time;
config.setProperty("timestamps.lastteleport", time);
config.save();
}
private long lastHealTimestamp;
private long _getLastHealTimestamp()
{
return config.getLong("timestamps.lastheal", 0);
}
public long getLastHealTimestamp()
{
return lastHealTimestamp;
}
public void setLastHealTimestamp(long time)
{
lastHealTimestamp = time;
config.setProperty("timestamps.lastheal", time);
config.save();
}
private String jail;
private String _getJail()
{
return config.getString("jail");
}
public String getJail()
{
return jail;
}
public void setJail(String jail)
{
if (jail == null || jail.isEmpty())
{
this.jail = null;
config.removeProperty("jail");
}
else
{
this.jail = jail;
config.setProperty("jail", jail);
}
config.save();
}
private List<String> mails;
private List<String> _getMails()
{
return config.getStringList("mail");
}
public List<String> getMails()
{
return mails;
}
public void setMails(List<String> mails)
{
if (mails == null)
{
config.removeProperty("mail");
mails = _getMails();
}
else
{
config.setProperty("mail", mails);
}
this.mails = mails;
config.save();
}
public void addMail(String mail)
{
mails.add(mail);
setMails(mails);
}
private boolean teleportEnabled;
private boolean getTeleportEnabled()
{
return config.getBoolean("teleportenabled", true);
}
public boolean isTeleportEnabled()
{
return teleportEnabled;
}
public void setTeleportEnabled(boolean set)
{
teleportEnabled = set;
config.setProperty("teleportenabled", set);
config.save();
}
public boolean toggleTeleportEnabled()
{
boolean ret = !isTeleportEnabled();
setTeleportEnabled(ret);
return ret;
}
public boolean toggleSocialSpy()
{
boolean ret = !isSocialSpyEnabled();
setSocialSpyEnabled(ret);
return ret;
}
private List<String> ignoredPlayers;
public List<String> getIgnoredPlayers()
{
return Collections.synchronizedList(config.getStringList("ignore"));
}
public void setIgnoredPlayers(List<String> players)
{
if (players == null || players.isEmpty())
{
ignoredPlayers = Collections.synchronizedList(new ArrayList<String>());
config.removeProperty("ignore");
}
else
{
ignoredPlayers = players;
config.setProperty("ignore", players);
}
config.save();
}
private boolean godmode;
private boolean _getGodModeEnabled()
{
return config.getBoolean("godmode", false);
}
public boolean isGodModeEnabled()
{
return godmode;
}
public void setGodModeEnabled(boolean set)
{
godmode = set;
config.setProperty("godmode", set);
config.save();
}
private boolean muted;
public boolean getMuted()
{
return config.getBoolean("muted", false);
}
public boolean isMuted()
{
return muted;
}
public void setMuted(boolean set)
{
muted = set;
config.setProperty("muted", set);
config.save();
}
private long muteTimeout;
private long _getMuteTimeout()
{
return config.getLong("timestamps.mute", 0);
}
public long getMuteTimeout()
{
return muteTimeout;
}
public void setMuteTimeout(long time)
{
muteTimeout = time;
config.setProperty("timestamps.mute", time);
config.save();
}
private boolean jailed;
private boolean getJailed()
{
return config.getBoolean("jailed", false);
}
public boolean isJailed()
{
return jailed;
}
public void setJailed(boolean set)
{
jailed = set;
config.setProperty("jailed", set);
config.save();
}
public boolean toggleJailed()
{
boolean ret = !isJailed();
setJailed(ret);
return ret;
}
private long jailTimeout;
private long _getJailTimeout()
{
return config.getLong("timestamps.jail", 0);
}
public long getJailTimeout()
{
return jailTimeout;
}
public void setJailTimeout(long time)
{
jailTimeout = time;
config.setProperty("timestamps.jail", time);
config.save();
}
public String getBanReason()
{
return config.getString("ban.reason");
}
public void setBanReason(String reason)
{
config.setProperty("ban.reason", Util.sanitizeString(reason));
config.save();
}
public long getBanTimeout()
{
return config.getLong("ban.timeout", 0);
}
public void setBanTimeout(long time)
{
config.setProperty("ban.timeout", time);
config.save();
}
private long lastLogin;
private long _getLastLogin()
{
return config.getLong("timestamps.login", 0);
}
public long getLastLogin()
{
return lastLogin;
}
private void _setLastLogin(long time)
{
lastLogin = time;
config.setProperty("timestamps.login", time);
}
private long lastLogout;
private long _getLastLogout()
{
return config.getLong("timestamps.logout", 0);
}
public long getLastLogout()
{
return lastLogout;
}
public void setLastLogout(long time)
{
lastLogout = time;
config.setProperty("timestamps.logout", time);
config.save();
}
private String lastLoginAddress;
private String _getLastLoginAddress()
{
return config.getString("ipAddress", "");
}
public String getLastLoginAddress()
{
return lastLoginAddress;
}
private void _setLastLoginAddress(String address)
{
lastLoginAddress = address;
config.setProperty("ipAddress", address);
}
private boolean afk;
private boolean getAfk()
{
return config.getBoolean("afk", false);
}
public boolean isAfk()
{
return afk;
}
public void setAfk(boolean set)
{
afk = set;
config.setProperty("afk", set);
config.save();
}
public boolean toggleAfk()
{
boolean ret = !isAfk();
setAfk(ret);
return ret;
}
private boolean newplayer;
private String geolocation;
private String _getGeoLocation()
{
return config.getString("geolocation");
}
public String getGeoLocation()
{
return geolocation;
}
public void setGeoLocation(String geolocation)
{
if (geolocation == null || geolocation.isEmpty())
{
this.geolocation = null;
config.removeProperty("geolocation");
}
else
{
this.geolocation = geolocation;
config.setProperty("geolocation", geolocation);
}
config.save();
}
private boolean isSocialSpyEnabled;
private boolean _isSocialSpyEnabled()
{
return config.getBoolean("socialspy", false);
}
public boolean isSocialSpyEnabled()
{
return isSocialSpyEnabled;
}
public void setSocialSpyEnabled(boolean status)
{
isSocialSpyEnabled = status;
config.setProperty("socialspy", status);
config.save();
}
private boolean isNPC;
private boolean _isNPC()
{
return config.getBoolean("npc", false);
}
public boolean isNPC()
{
return isNPC;
}
public void setNPC(boolean set)
{
isNPC = set;
config.setProperty("npc", set);
config.save();
}
private boolean arePowerToolsEnabled;
public boolean arePowerToolsEnabled()
{
return arePowerToolsEnabled;
}
public void setPowerToolsEnabled(boolean set)
{
arePowerToolsEnabled = set;
config.setProperty("powertoolsenabled", set);
config.save();
}
public boolean togglePowerToolsEnabled()
{
boolean ret = !arePowerToolsEnabled();
setPowerToolsEnabled(ret);
return ret;
}
private boolean _arePowerToolsEnabled()
{
return config.getBoolean("powertoolsenabled", true);
}
private ConfigurationSection kitTimestamps;
private ConfigurationSection _getKitTimestamps()
{
if (config.isConfigurationSection("timestamps.kits"))
{
final ConfigurationSection section = config.getConfigurationSection("timestamps.kits");
final ConfigurationSection newSection = new MemoryConfiguration();
for (String command : section.getKeys(false))
{
if (section.isLong(command))
{
newSection.set(command.toLowerCase(Locale.ENGLISH), section.getLong(command));
}
else if (section.isInt(command))
{
newSection.set(command.toLowerCase(Locale.ENGLISH), (long)section.getInt(command));
}
}
return newSection;
}
return new MemoryConfiguration();
}
public Set<String> getKitTimestamps() {
return kitTimestamps.getKeys(false);
}
public long getKitTimestamp(String name)
{
name = name.replace('.', '_').replace('/', '_');
if (kitTimestamps != null)
{
return kitTimestamps.getLong(name, 0l);
}
return 0l;
}
public void setKitTimestamp(final String name, final long time)
{
kitTimestamps.set(name.toLowerCase(Locale.ENGLISH), time);
config.setProperty("timestamps.kits", kitTimestamps);
config.save();
}
public void save()
{
config.save();
}
}

View File

@@ -0,0 +1,111 @@
package com.earth2me.essentials;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;
import java.util.logging.Logger;
import java.util.regex.Pattern;
public class Util
{
private Util()
{
}
private final static Logger logger = Logger.getLogger("Minecraft");
private final static Pattern INVALIDFILECHARS = Pattern.compile("[^a-z0-9]");
private final static Pattern INVALIDCHARS = Pattern.compile("[^\t\n\r\u0020-\u007E\u0085\u00A0-\uD7FF\uE000-\uFFFC]");
//Used to clean file names before saving to disk
public static String sanitizeFileName(final String name)
{
return safeString(name);
}
//Used to clean strings/names before saving as filenames/permissions
public static String safeString(final String string)
{
return INVALIDFILECHARS.matcher(string.toLowerCase(Locale.ENGLISH)).replaceAll("_");
}
//Less restrictive string sanitizing, when not used as perm or filename
public static String sanitizeString(final String string)
{
return INVALIDCHARS.matcher(string).replaceAll("");
}
private static DecimalFormat dFormat = new DecimalFormat("#0.00", DecimalFormatSymbols.getInstance(Locale.US));
public static String formatAsCurrency(final double value)
{
String str = dFormat.format(value);
if (str.endsWith(".00"))
{
str = str.substring(0, str.length() - 3);
}
return str;
}
public static double roundDouble(final double d)
{
return Math.round(d * 100.0) / 100.0;
}
public static boolean isInt(final String sInt)
{
try
{
Integer.parseInt(sInt);
}
catch (NumberFormatException e)
{
return false;
}
return true;
}
public static String joinList(Object... list)
{
return joinList(", ", list);
}
public static String joinList(String seperator, Object... list)
{
StringBuilder buf = new StringBuilder();
for (Object each : list)
{
if (buf.length() > 0)
{
buf.append(seperator);
}
if (each instanceof Collection)
{
buf.append(joinList(seperator, ((Collection)each).toArray()));
}
else
{
try
{
buf.append(each.toString());
}
catch (Exception e)
{
buf.append(each.toString());
}
}
}
return buf.toString();
}
public static String lastCode(final String input)
{
int pos = input.lastIndexOf("§");
if (pos == -1 || (pos + 1) == input.length())
{
return "";
}
return input.substring(pos, pos + 2);
}
}