Implement a mini-application inside the Google Sheets module that provides an easy way to get skin data

This commit is contained in:
Sam 2017-07-30 16:11:18 +01:00
parent c7e4d5b00b
commit 4f6d24dea8
9 changed files with 399 additions and 80 deletions

View File

@ -13,6 +13,12 @@
<artifactId>mineplex-google-sheets</artifactId>
<dependencies>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>

View File

@ -1,74 +1,28 @@
package mineplex.googlesheets;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.awt.*;
import org.json.JSONObject;
import mineplex.googlesheets.sheetparser.SheetProviderImpl;
import mineplex.googlesheets.skinhelper.SkinHelperUI;
public class GoogleSheetController
{
private static final int SLEEP_TIME = 1000;
private static final String DATA_STORE_DIR = ".." + File.separatorChar + ".." + File.separatorChar + "update" + File.separatorChar + "files";
public static void main(String[] args) throws InterruptedException
public static void main(String[] args)
{
String sheetToRead = System.getProperty("sheet");
SpreadsheetType[] types;
String module = System.getProperty("module");
if (sheetToRead == null)
if (module == null || module.equalsIgnoreCase("sheetparser"))
{
types = SpreadsheetType.values();
new SheetProviderImpl();
}
else
else if (module.equalsIgnoreCase("skinhelper"))
{
types = new SpreadsheetType[]{SpreadsheetType.valueOf(sheetToRead)};
}
System.out.println("Loading Sheet Provider");
SheetProvider provider = new SheetProvider();
System.out.println("Loaded Sheet Provider");
for (SpreadsheetType type : types)
{
System.out.println("Sleeping...");
Thread.sleep(SLEEP_TIME);
System.out.println("Getting data for " + type.name() + " (" + type.getID() + ")");
JSONObject object = provider.asJSONObject(type);
System.out.println("Done");
System.out.println("Saving to file...");
File dir = new File(DATA_STORE_DIR);
File file = new File(dir + File.separator + type.name() + ".json");
if (!dir.exists())
EventQueue.invokeLater(() ->
{
System.out.println("mkdir");
dir.mkdirs();
}
try
{
System.out.println("Deleting");
file.delete();
System.out.println("new File");
file.createNewFile();
FileWriter writer = new FileWriter(file);
System.out.println("Writing");
writer.write(object.toString());
System.out.println("Closing...");
writer.close();
}
catch (IOException e)
{
e.printStackTrace();
}
SkinHelperUI frame = new SkinHelperUI();
frame.setVisible(true);
});
}
}

View File

@ -19,7 +19,7 @@ public enum SpreadsheetType
_id = id;
}
public String getID()
public String getId()
{
return _id;
}

View File

@ -1,11 +1,11 @@
package mineplex.googlesheets;
package mineplex.googlesheets.sheetparser;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -28,13 +28,19 @@ import com.google.api.services.sheets.v4.SheetsScopes;
import com.google.api.services.sheets.v4.model.Sheet;
import com.google.api.services.sheets.v4.model.Spreadsheet;
import mineplex.googlesheets.SpreadsheetType;
/**
* A provider class designed with the functionality to get data from a <a href="https://docs.google.com/spreadsheets">Google Spreadsheet</a>.<br>
* Then proceed to return this data within the context of a {@link JSONObject}.
*/
public class SheetProvider
{
/** Application name. */
/** Service name. */
private static final String APPLICATION_NAME = "Mineplex Google Sheets";
/** Directory to store user credentials for this application. */
/** Directory to store user credentials for the service. */
private static final File DATA_STORE_DIR = new File(".." + File.separatorChar + ".." + File.separatorChar + "update" + File.separatorChar + "files");
/** Global instance of the {@link FileDataStoreFactory}. */
@ -46,7 +52,8 @@ public class SheetProvider
/** Global instance of the HTTP transport. */
private static HttpTransport HTTP_TRANSPORT;
private static final List<String> SCOPES = Arrays.asList(SheetsScopes.SPREADSHEETS);
/** List of all permissions that the service requires */
private static final List<String> SCOPES = Collections.singletonList(SheetsScopes.SPREADSHEETS);
private Sheets _service;
private Credential _credential;
@ -81,9 +88,9 @@ public class SheetProvider
* Creates an authorized Credential object.
*
* @return an authorized Credential object.
* @throws IOException
* @throws IOException If the Credential fails to authorise.
*/
public Credential authorize() throws IOException
private Credential authorize() throws IOException
{
// Load client secrets.
InputStream in = new FileInputStream(DATA_STORE_DIR + File.separator + "client_secret.json");
@ -91,28 +98,37 @@ public class SheetProvider
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES).setDataStoreFactory(DATA_STORE_FACTORY).setAccessType("offline").build();
Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
return credential;
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
}
/**
* Build and return an authorized Sheets API client service.
*
* @return an authorized Sheets API client service
* @throws IOException
*/
public Sheets getSheetsService()
private Sheets getSheetsService()
{
return new Sheets.Builder(HTTP_TRANSPORT, JSON_FACTORY, _credential).setApplicationName(APPLICATION_NAME).build();
}
/**
* Returns the data comprised inside a Google Spreadsheet in the context of a {@link JSONObject}.
*
* @param spreadsheet The {@link SpreadsheetType} that you need to get the data from.
* @return A {@link JSONObject} containing all the data about a spreadsheet mapped by sheet -> rows -> columns.
*/
public JSONObject asJSONObject(SpreadsheetType spreadsheet)
{
JSONObject parent = new JSONObject();
JSONArray array = new JSONArray();
Map<String, List<List<Object>>> valuesMap = get(spreadsheet);
for (String sheetName : valuesMap.keySet())
if (valuesMap == null)
{
return null;
}
valuesMap.forEach((sheetName, lists) ->
{
List<List<Object>> values = valuesMap.get(sheetName);
@ -122,20 +138,21 @@ public class SheetProvider
object.put("values", values);
array.put(object);
}
});
parent.put("data", array);
return parent;
}
public Map<String, List<List<Object>>> get(SpreadsheetType spreadsheet)
private Map<String, List<List<Object>>> get(SpreadsheetType spreadsheet)
{
try
{
Spreadsheet googleSpreadsheet = _service.spreadsheets().get(spreadsheet.getID()).execute();
Map<String, List<List<Object>>> valuesMap = new HashMap<>(googleSpreadsheet.getSheets().size() - 1);
Spreadsheet googleSpreadsheet = _service.spreadsheets().get(spreadsheet.getId()).execute();
List<Sheet> sheets = googleSpreadsheet.getSheets();
Map<String, List<List<Object>>> valuesMap = new HashMap<>(sheets.size());
for (Sheet sheet : googleSpreadsheet.getSheets())
for (Sheet sheet : sheets)
{
String name = sheet.getProperties().getTitle();
@ -152,9 +169,9 @@ public class SheetProvider
return null;
}
public List<List<Object>> get(SpreadsheetType spreadsheet, String sheetName) throws IOException
private List<List<Object>> get(SpreadsheetType spreadsheet, String sheetName) throws IOException
{
return _service.spreadsheets().values().get(spreadsheet.getID(), sheetName).execute().getValues();
return _service.spreadsheets().values().get(spreadsheet.getId(), sheetName).execute().getValues();
}
}

View File

@ -0,0 +1,82 @@
package mineplex.googlesheets.sheetparser;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import org.json.JSONObject;
import mineplex.googlesheets.SpreadsheetType;
public class SheetProviderImpl
{
private static final int SLEEP_TIME = 1000;
private static final String DATA_STORE_DIR = ".." + File.separatorChar + ".." + File.separatorChar + "update" + File.separatorChar + "files";
public SheetProviderImpl()
{
String sheetToRead = System.getProperty("sheet");
SpreadsheetType[] types;
if (sheetToRead == null)
{
types = SpreadsheetType.values();
}
else
{
types = new SpreadsheetType[]{SpreadsheetType.valueOf(sheetToRead)};
}
System.out.println("Loading Sheet Provider");
SheetProvider provider = new SheetProvider();
System.out.println("Loaded Sheet Provider");
for (SpreadsheetType type : types)
{
System.out.println("Sleeping...");
try
{
Thread.sleep(SLEEP_TIME);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("Getting data for " + type.name() + " (" + type.getId() + ")");
JSONObject object = provider.asJSONObject(type);
System.out.println("Done");
System.out.println("Saving to file...");
File dir = new File(DATA_STORE_DIR);
File file = new File(dir + File.separator + type.name() + ".json");
if (!dir.exists())
{
dir.mkdirs();
}
try
{
System.out.println("Deleting");
file.delete();
System.out.println("Creating a new file");
file.createNewFile();
FileWriter writer = new FileWriter(file);
System.out.println("Writing");
writer.write(object.toString());
System.out.println("Closing...");
writer.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,115 @@
package mineplex.googlesheets.skinhelper;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.concurrent.TimeUnit;
import mineplex.googlesheets.util.SkinFetcher;
import mineplex.googlesheets.util.UUIDFetcher;
public class SkinHelperUI extends JFrame
{
private static final Font FONT = new Font("Verdana", Font.PLAIN, 12);
private static final long FETCH_WAIT_TIME = 30;
private static final long FETCH_WAIT_MILLISECONDS = TimeUnit.SECONDS.toMillis(FETCH_WAIT_TIME);
private long _lastFetch;
public SkinHelperUI()
{
setTitle("Skin Helper");
setResizable(false);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100, 100, 150, 300);
JPanel contentPane = new JPanel();
contentPane.setBackground(Color.DARK_GRAY);
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
JTextField txtMinecraftName = new JTextField();
txtMinecraftName.setFont(FONT);
txtMinecraftName.setForeground(Color.WHITE);
txtMinecraftName.setBackground(Color.GRAY);
txtMinecraftName.setBounds(10, 55, 124, 20);
txtMinecraftName.setColumns(10);
contentPane.add(txtMinecraftName);
JLabel lblMinecraftName = new JLabel("Minecraft Name");
lblMinecraftName.setForeground(Color.WHITE);
lblMinecraftName.setFont(FONT);
lblMinecraftName.setBounds(10, 30, 100, 14);
contentPane.add(lblMinecraftName);
JButton btnOk = new JButton("OK");
btnOk.setForeground(Color.WHITE);
btnOk.setBackground(Color.DARK_GRAY);
btnOk.setBounds(10, 86, 124, 23);
contentPane.add(btnOk);
JLabel lblSkinValue = new JLabel("Skin Value");
lblSkinValue.setForeground(Color.WHITE);
lblSkinValue.setFont(FONT);
lblSkinValue.setBounds(10, 120, 100, 14);
contentPane.add(lblSkinValue);
JTextField txtSkinValue = new JTextField();
txtSkinValue.setEditable(false);
txtSkinValue.setForeground(Color.WHITE);
txtSkinValue.setBackground(Color.GRAY);
txtSkinValue.setFont(FONT);
txtSkinValue.setColumns(10);
txtSkinValue.setBounds(10, 145, 124, 20);
contentPane.add(txtSkinValue);
JLabel lblSkinSignature = new JLabel("Skin Signature");
lblSkinSignature.setForeground(Color.WHITE);
lblSkinSignature.setFont(FONT);
lblSkinSignature.setBounds(10, 176, 100, 14);
contentPane.add(lblSkinSignature);
JTextField txtSkinSignature = new JTextField();
txtSkinSignature.setEditable(false);
txtSkinSignature.setForeground(Color.WHITE);
txtSkinSignature.setBackground(Color.GRAY);
txtSkinSignature.setFont(FONT);
txtSkinSignature.setColumns(10);
txtSkinSignature.setBounds(10, 201, 124, 20);
contentPane.add(txtSkinSignature);
btnOk.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent event)
{
if (System.currentTimeMillis() - _lastFetch < FETCH_WAIT_MILLISECONDS)
{
JOptionPane.showMessageDialog(SkinHelperUI.this, "You must wait a minimum of " + FETCH_WAIT_TIME + " seconds between each skin fetch to prevent you from being blocked from using the Mojang API.");
return;
}
txtSkinValue.setText("Fetching...");
txtSkinSignature.setText(txtSkinValue.getText());
try
{
String[] skinData = SkinFetcher.getSkinData(UUIDFetcher.getPlayerUUIDNoDashes(txtMinecraftName.getText()));
txtSkinValue.setText(skinData[0]);
txtSkinSignature.setText(skinData[1]);
_lastFetch = System.currentTimeMillis();
}
catch (Exception e)
{
e.printStackTrace();
JOptionPane.showMessageDialog(SkinHelperUI.this, "Please check the Minecraft Name you have entered. If it is correct please wait a minute and try again.");
}
}
});
}
}

View File

@ -0,0 +1,42 @@
package mineplex.googlesheets.util;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
public class SkinFetcher
{
private static final String SKIN_URL = "https://sessionserver.mojang.com/session/minecraft/profile/UUID?unsigned=false";
public static String[] getSkinData(String uuid) throws Exception
{
String[] skinData = new String[2];
JSONObject object = UtilJSON.getFromURL(SKIN_URL.replaceFirst("UUID", uuid));
JSONArray properties = (JSONArray) object.get("properties");
System.out.println(properties.size());
for (Object o : properties)
{
System.out.println(o.toString());
}
JSONObject innerObject = (JSONObject) properties.get(0);
System.out.println(innerObject.size());
for (Object o : innerObject.entrySet())
{
System.out.println(o.toString());
}
skinData[1] = (String) innerObject.get("signature");
skinData[0] = (String) innerObject.get("value");
System.out.println(skinData[0]);
System.out.println(skinData[1]);
return skinData;
}
}

View File

@ -0,0 +1,62 @@
package mineplex.googlesheets.util;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
/**
* A utility to fetch UUIDs based on a player's name.
* Adapted from UUIDFetcher inside Core.Common.
*/
public class UUIDFetcher
{
private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
private static final JSONParser PARSER = new JSONParser();
public static String getPlayerUUIDNoDashes(String name) throws Exception
{
String uuid = null;
List<String> nameList = Collections.singletonList(name);
HttpURLConnection connection = createConnection();
String body = JSONArray.toJSONString(nameList);
writeBody(connection, body);
JSONArray array = (JSONArray) PARSER.parse(new InputStreamReader(connection.getInputStream()));
for (Object profile : array)
{
JSONObject jsonProfile = (JSONObject) profile;
uuid = (String) jsonProfile.get("id");
}
return uuid;
}
private static void writeBody(HttpURLConnection connection, String body) throws Exception
{
OutputStream stream = connection.getOutputStream();
stream.write(body.getBytes());
stream.flush();
stream.close();
}
private static HttpURLConnection createConnection() throws Exception
{
URL url = new URL(PROFILE_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection;
}
}

View File

@ -0,0 +1,41 @@
package mineplex.googlesheets.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.Charset;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
public class UtilJSON
{
private static final JSONParser PARSER = new JSONParser();
private static String readAll(Reader rd) throws IOException
{
StringBuilder sb = new StringBuilder();
int cp;
while ((cp = rd.read()) != -1)
{
sb.append((char) cp);
}
return sb.toString();
}
public static JSONObject getFromURL(String url) throws IOException, ParseException
{
try (InputStream is = new URL(url).openStream())
{
BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
String jsonText = readAll(rd);
return (JSONObject) PARSER.parse(jsonText);
}
}
}