Compare commits

...

11 Commits

53 changed files with 2022 additions and 1423 deletions

View File

@ -14,8 +14,8 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>

View File

@ -12,30 +12,5 @@ public class App {
MainWindow window = new MainWindow();
window.run();
/*
try {
Document obedy = Jsoup.connect("https://objednavky.jidelnasokolska.cz/faces/secured/month.jsp?terminal=false&keyboard=&printer=")
.header("Connection", "keep-alive")
.cookie("XSRF-TOKEN", XSRF_TOKEN)
.cookie("JSESSIONID", jidelnaJSESSIONID)
.get();
System.out.println(obedy.toString());
//#endregion
} catch (IOException e) {
// TO DO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e)
{
e.printStackTrace();
}*/
}
}

View File

@ -18,8 +18,11 @@ import com.googlecode.lanterna.gui2.dialogs.DialogWindow;
import com.googlecode.lanterna.gui2.dialogs.MessageDialog;
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton;
import xyz.thastertyn.Tuples.Triplet;
import xyz.thastertyn.Types.InputtedCredentials;
/**
* A Dialog window for inputing username and password.
*/
public class CredentialsInput extends DialogWindow {
private TextBox username;
@ -101,18 +104,21 @@ public class CredentialsInput extends DialogWindow {
.addComponent(password)
.addTo(mainPanel);
Panel rememberPanel = new Panel()
.setLayoutManager(new GridLayout(3))
.setLayoutData(GridLayout.createLayoutData(
GridLayout.Alignment.FILL,
Alignment.CENTER,
true,
false));
rememberPanel.addComponent(new Label("Rembember?"))
.addComponent(remember)
.addTo(mainPanel);
if(!LocalCredentials.getInstance().checkForExistingCredentials())
{
Panel rememberPanel = new Panel()
.setLayoutManager(new GridLayout(3))
.setLayoutData(GridLayout.createLayoutData(
GridLayout.Alignment.FILL,
Alignment.CENTER,
true,
false));
rememberPanel.addComponent(new Label("Rembember?"))
.addComponent(remember)
.addTo(mainPanel);
}
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
buttonPanel.setLayoutData(
@ -126,6 +132,9 @@ public class CredentialsInput extends DialogWindow {
setComponent(mainPanel);
}
/**
* User pressed ok, check if anything is actually typed and if so, pack and return it
*/
public void onOK()
{
this.user = username.getText();
@ -145,9 +154,13 @@ public class CredentialsInput extends DialogWindow {
close();
}
/**
* returns the inputted username, password and if user chose to keep credentials stored
* @return {@link InputtedCredentials} with username first, password second and whether to store the credentials third
*/
@Override
public Triplet<String, String, Boolean> showDialog(WindowBasedTextGUI textGUI) {
public InputtedCredentials showDialog(WindowBasedTextGUI textGUI) {
super.showDialog(textGUI);
return new Triplet<String,String,Boolean>(user, pass, remember.isChecked());
return new InputtedCredentials(user, pass, remember.isChecked());
}
}

View File

@ -0,0 +1,162 @@
package xyz.thastertyn.Login;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import xyz.thastertyn.Types.Credentials;
public class LocalCredentials {
private static LocalCredentials localCredentials = new LocalCredentials();
private String path;
private File credentialsFile;
private File credentialsPath;
/**
* Checks for already saved credentials, at least if its file exists <br>
* for Windows it checks {@code AppData\Roaming\jecnak\credemtials.txt} <br>
* for Linux it checks {@code ~/.local/share/jecnak/credentials.txt} <br>
* @return
* <ul>
* <li> {@code true} a file exists </li>
* <li> {@code false} doesn't exist </li>
* </ul>
*/
public boolean checkForExistingCredentials()
{
if(System.getProperty("os.name").equals("Linux"))
{
// /home/user/.local/share/jecnak/...
path = System.getProperty("user.home") + "/.local/share/jecnak/";
}else if(System.getProperty("os.name").contains("Windows"))
{
// C:\Users\\user\AppData\Roaming\...
path = System.getenv("APPDATA") + "\\jecnak\\";
}else{
return false;
}
credentialsPath = new File(path);
credentialsFile = new File(credentialsPath, "credentials.txt");
if(!credentialsFile.exists())
{
return false;
}
return true;
}
/**
* Try reading the credentails from a local file
* @return String[] with username on index 0, and password on index 1, <br>
* can also return null if credentials are corrupted, inaccessible, etc.
*/
public Credentials getCredentialsFile()
{
// TODO Use hashmap instead of array
String user = null;
String pass = null;
if(credentialsFile == null)
{
return null;
}
try {
BufferedReader reader = new BufferedReader(new FileReader(credentialsFile));
String line = "";
while((line = reader.readLine()) != null)
{
String[] currentValue = line.split("=");
if(currentValue[0].equals("user"))
{
user = currentValue[1];
}else if(currentValue[0].equals("pass"))
{
pass = currentValue[1];
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
return null;
}
if(user == null || pass == null)
{
return null;
}
return new Credentials(user, pass);
}
/**
* appends the current credentials to the file
* @param credentials the array containing username and password. Username is on index 0, password on index 1
* @return
*/
public boolean saveCredentials(Credentials credentials)
{
if(credentialsFile == null)
{
return false;
}
if(!credentialsFile.exists())
{
try{
credentialsPath.mkdirs();
credentialsFile.createNewFile();
}catch(IOException e)
{
return false;
}
}
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(credentialsFile));
writer.append(String.format(
"user=%s\npass=%s\n",
credentials.getUsername(),
credentials.getPassword()));
writer.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* Deletes the credentials file
* @return
*/
public boolean deleteCredentials()
{
credentialsFile.delete();
return true;
}
/**
* Returns a single instance to prevent creating and loading the files all over again when not much has changed
* @return {@link LocalCredentials} instance
*/
public static LocalCredentials getInstance()
{
return localCredentials;
}
}

View File

@ -12,101 +12,103 @@ import org.jsoup.Connection.Method;
import org.jsoup.nodes.Document;
import xyz.thastertyn.Scrape.Downloader;
import xyz.thastertyn.Types.Credentials;
public class Login {
private String Jsessionid = null;
// Check for session expiration
private long start;
private long lastCheck;
public void loginJecna(String user, String pass) throws UnknownHostException, IOException, CredentialException, TimeoutException
/**
* Tries logging into SPSE Jecna. If it succeeds it also saves the JSessionId into {@link Downloader} for ease of use. If it fails to login, the program is as good as not running at all
* @param credentials {@link Credentials} for username anas password
* @throws UnknownHostException Most likely no internet connection, spsejecna.cz could not be recognised as a host
* @throws IOException by Jsoup
* @throws CredentialException The username or password is incorrect
* @throws TimeoutException Connection timed out, most likely a very slow internet
*/
public void loginJecna(Credentials credentials) throws UnknownHostException, IOException, CredentialException, TimeoutException
{
//#region JSESSIONID
Connection.Response response = Jsoup.connect("https://www.spsejecna.cz")
.header("Connection", "keep-alive")
.method(Method.HEAD)
.execute();
//#region JSESSIONID
Connection.Response response = Jsoup.connect("https://www.spsejecna.cz")
.header("Connection", "keep-alive")
.method(Method.HEAD)
.execute();
Jsessionid = response.cookie("JSESSIONID");
Downloader.setJSessionId(Jsessionid);
//#endregion
Jsessionid = response.cookie("JSESSIONID");
Downloader.setJSessionId(Jsessionid);
//#endregion
//#region Token3
String token3 = Downloader.download("https://www.spsejecna.cz/user/role?role=student")
.get()
.select("input[name=token3]")
.attr("value");
//#endregion
//#region Token3
String token3 = Downloader.getConnection("https://www.spsejecna.cz/user/role?role=student")
.get()
.select("input[name=token3]")
.attr("value");
//#endregion
//#region Login
Downloader.download("https://www.spsejecna.cz/user/login")
.method(Connection.Method.POST)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Origin", "https://www.spsejecna.cz")
.data("token3", token3)
.data("user", user)
.data("pass", pass)
.data("submit", "P%C5%99ihl%C3%A1sit+se")
.followRedirects(true)
.execute();
//#endregion
//#region Login
Downloader.getConnection("https://www.spsejecna.cz/user/login")
.method(Connection.Method.POST)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Origin", "https://www.spsejecna.cz")
.data("token3", token3)
.data("user", credentials.getUsername())
.data("pass", credentials.getPassword())
.data("submit", "P%C5%99ihl%C3%A1sit+se")
.followRedirects(true)
.execute();
//#endregion
Document test = Downloader.download("https://www.spsejecna.cz/score/student")
.get();
Document test = Downloader.getConnection("https://www.spsejecna.cz/score/student")
.get();
if(test.toString().contains("Pro pokračování se přihlaste do systému"))
{
throw new CredentialException("Incorrect username or password");
}
start = System.currentTimeMillis() / 1000L;
lastCheck = start;
if(test.toString().contains("Pro pokračování se přihlaste do systému"))
{
throw new CredentialException("Incorrect username or password");
}
}
public void loginJidelna(String user, String pass) throws UnknownHostException, IOException
{
//#region JSESSIONID
Connection.Response jidelna = Jsoup.connect("https://objednavky.jidelnasokolska.cz/")
.header("Connection", "keep-alive")
.method(Method.HEAD)
.execute();
//#region JSESSIONID
Connection.Response jidelna = Jsoup.connect("https://objednavky.jidelnasokolska.cz/")
.header("Connection", "keep-alive")
.method(Method.HEAD)
.execute();
String jidelnaJSESSIONID = jidelna.cookie("JSESSIONID");
String XSRF_TOKEN = jidelna.cookie("XSRF-TOKEN");
//#endregion
String jidelnaJSESSIONID = jidelna.cookie("JSESSIONID");
String XSRF_TOKEN = jidelna.cookie("XSRF-TOKEN");
//#endregion
//#region CSRF
String csrf = Jsoup.connect("https://objednavky.jidelnasokolska.cz/faces/login.jsp")
.header("Connection", "keep-alive")
.cookie("XSRF-TOKEN", XSRF_TOKEN)
.cookie("JSESSIONID", jidelnaJSESSIONID)
.get()
.select("input[name=_csrf]")
.attr("value");
//#region CSRF
String csrf = Jsoup.connect("https://objednavky.jidelnasokolska.cz/faces/login.jsp")
.header("Connection", "keep-alive")
.cookie("XSRF-TOKEN", XSRF_TOKEN)
.cookie("JSESSIONID", jidelnaJSESSIONID)
.get()
.select("input[name=_csrf]")
.attr("value");
if(!XSRF_TOKEN.equals(csrf))
{
throw new SecurityException("CSRF tokens do not match, something is up");
}
//#endregion
if(!XSRF_TOKEN.equals(csrf))
{
throw new SecurityException("CSRF tokens do not match, something is up");
}
//#endregion
//#region Login
Jsoup.connect("https://objednavky.jidelnasokolska.cz/j_spring_security_check")
.header("Connection", "keep-alive")
.header("Content-Type", "application/x-www-form-urlencoded")
.cookie("XSRF-TOKEN", XSRF_TOKEN)
.cookie("JSESSIONID", jidelnaJSESSIONID)
.data("j_username", user)
.data("j_password", pass)
.data("terminal", "false")
.data("type", "web")
.data("_csrf", XSRF_TOKEN)
.data("targetUrl", "/faces/secured/main.jsp?terminal=false&status=true&printer=false&keyboard=false")
.method(Method.POST)
.execute();
//#endregion
//#region Login
Jsoup.connect("https://objednavky.jidelnasokolska.cz/j_spring_security_check")
.header("Connection", "keep-alive")
.header("Content-Type", "application/x-www-form-urlencoded")
.cookie("XSRF-TOKEN", XSRF_TOKEN)
.cookie("JSESSIONID", jidelnaJSESSIONID)
.data("j_username", user)
.data("j_password", pass)
.data("terminal", "false")
.data("type", "web")
.data("_csrf", XSRF_TOKEN)
.data("targetUrl", "/faces/secured/main.jsp?terminal=false&status=true&printer=false&keyboard=false")
.method(Method.POST)
.execute();
//#endregion
}
}

View File

@ -1,10 +1,5 @@
package xyz.thastertyn.Login;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.concurrent.TimeoutException;
@ -15,12 +10,17 @@ import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import com.googlecode.lanterna.gui2.dialogs.MessageDialog;
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton;
import xyz.thastertyn.Tuples.Triplet;
import xyz.thastertyn.Types.Credentials;
import xyz.thastertyn.Types.InputtedCredentials;
/**
* Merges the functionality of {@link LocalCredentials}, {@link CredentialsInput}, and {@link Login}
*/
public class LoginController {
private WindowBasedTextGUI textGUI;
private xyz.thastertyn.Login.CredentialsInput dialog;
private LocalCredentials localCredentials = LocalCredentials.getInstance();
private xyz.thastertyn.Login.Login login = new xyz.thastertyn.Login.Login();
public LoginController(WindowBasedTextGUI textGUI)
@ -28,165 +28,72 @@ public class LoginController {
this.textGUI = textGUI;
}
public void login()
/**
* Picks appropriate method for logging in
* <ul>
* <li> Locally stored credentials exist, then try using them. If they arent in good shape, use GUI
* <li> Use GUI and ask the user for credentials
* </ul>
*/
public void login(boolean forceGUI)
{
Credentials credentials;
if(!forceGUI && localCredentials.checkForExistingCredentials())
{
credentials = localCredentials.getCredentialsFile();
}else{
credentials = loginUsingGui();
}
useCredentials(credentials);
}
public Credentials loginUsingGui()
{
dialog = new CredentialsInput();
if(checkForCredentials()) // Credentials exist
InputtedCredentials credentials = dialog.showDialog(textGUI);
if(credentials.save())
{
if(loginUsingCredentials()) // They are accessible and can read them
{
return;
}
localCredentials.saveCredentials(credentials.getCredentials());
}
Triplet<String, String, Boolean> data = dialog.showDialog(textGUI); // Failed to get credentials to log in, get them from user
try
{
login.loginJecna(data.getValue0(), data.getValue1());
}catch (TimeoutException e)
{
MessageDialog.showMessageDialog(textGUI, "Timeout", "The attempt to connect took too long.", MessageDialogButton.Retry,
MessageDialogButton.Abort);
} catch (UnknownHostException e) {
MessageDialog.showMessageDialog(textGUI, "No Internet connection",
"There seems to be no internet connection, reverting to cached data",
MessageDialogButton.OK);
login();
} catch (CredentialException e)
{
MessageDialog.showMessageDialog(textGUI, "Incorrect username or password",
"The username or password you entered is incorrect",
MessageDialogButton.OK);
login();
} catch (IOException e)
{
MessageDialog.showMessageDialog(textGUI, "There was an error",
"Maybe try again and it will go away",
MessageDialogButton.Retry);
login();
}
return credentials.getCredentials();
}
/**
* Zkontroluje zda jiz neexistuji ulozene udaje na systemu
* @return <ul>
* <li> {@code true} soubor existuje a neni prazdny </li>
* <li> {@code false} neexistuje, nebo je prazdny </li>
* </ul>
* Credentials were successfully obtained and are used for logging in, although a lot can go wrong
* @param credentials
*/
private boolean checkForCredentials()
private void useCredentials(Credentials credentials)
{
return false;
//File credentials = null;
//if(System.getProperty("os.name").equals("Linux"))
//{
// credentials = new File("~/.local/share/jecnak/credentials.json");
//}else if(System.getProperty("os.name").contains("Windows"))
//{
// credentials = new File(System.getenv("APPDATA\\jecnak\\"));
//}
//if(!credentials.exists() || credentials.length() == 0)
//{
// return false;
//}
//return true;
}
public boolean saveCredentials(String user, String pass)
{
File credentials;
if(System.getProperty("os.name").equals("Linux"))
{
credentials = new File("~/.local/share/jecnak/credentials.json");
}else if(System.getProperty("os.name").contains("Windows"))
{
credentials = new File(System.getenv("APPDATA\\jecnak\\"));
}else{
return false;
}
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(credentials));
writer.append("username=" + user);
writer.append("password=" + pass);
writer.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
private boolean loginUsingCredentials()
{
File credentials;
if(System.getProperty("os.name").equals("Linux"))
login.loginJecna(credentials);
}catch (TimeoutException e)
{
credentials = new File("~/.local/share/jecnak/credentials.json");
}else if(System.getProperty("os.name").contains("Windows"))
MessageDialog.showMessageDialog(textGUI, "Timeout", "The attempt to connect took too long. The app will quit", MessageDialogButton.OK);
System.exit(0);
} catch (UnknownHostException e) {
MessageDialog.showMessageDialog(textGUI, "No Internet connection. The app will quit",
e.getMessage(),
MessageDialogButton.OK);
System.exit(0);
} catch (CredentialException e)
{
credentials = new File(System.getenv("APPDATA\\jecnak\\"));
}else{
return false;
}
if(!credentials.exists() || credentials.length() == 0)
MessageDialog.showMessageDialog(textGUI, "Incorrect username or password",
e.getMessage(),
MessageDialogButton.OK);
// The credentials were most likely tampered with after save, just get rid of them and save them another time
LocalCredentials.getInstance().deleteCredentials();
login(true);
} catch (IOException e)
{
return false;
MessageDialog.showMessageDialog(textGUI, "There was an error. The app will quit",
e.getMessage(),
MessageDialogButton.Retry);
System.exit(0);
}
String user = "";
String pass = "";
try {
BufferedReader reader = new BufferedReader(new FileReader(credentials));
String line = "";
while((line = reader.readLine()) != null)
{
if(line.matches("^[a-z]\\=.*$"))
{
String key = line.split("=")[0];
String value = line.split("=")[1];
switch(key)
{
case "username":
user = value;
break;
case "password":
pass = value;
break;
default:
continue;
}
}else{
continue;
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
try {
login.loginJecna(user, pass);
} catch (CredentialException | IOException | TimeoutException e) {
e.printStackTrace();
return false;
}
return true;
}
}

View File

@ -0,0 +1,123 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.Arrays;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Option;
import xyz.thastertyn.Types.Options;
public class AbsenceList extends JecnaScrape {
private ArrayList<xyz.thastertyn.Types.AbsenceList> data;
private Choice currentChoice;
private Options schoolYearOptions;
@Override
public void download() throws IOException
{
parse("https://www.spsejecna.cz/absence/student");
}
@Override
public void download(Choice choice) throws IOException {
currentChoice = choice;
parse(String.format(
"https://www.spsejecna.cz/absence/student?schoolYearId=%s",
choice.getChoices().get(0)));
}
private void parse(String url) throws IOException
{
data = new ArrayList<>();
schoolYearOptions = new Options("Skolni R.");
Document absenceListHTMLDocument = Downloader.download(url);
Elements absenceLists = absenceListHTMLDocument.select("table.absence-list").select("tbody").select("tr");
for(Element e : absenceLists)
{
String date = e.child(0).text();
String text = e.child(1).text();
data.add(
new xyz.thastertyn.Types.AbsenceList(
date, text));
}
Elements options = absenceListHTMLDocument.select("form.listConfigure").select("select[id=schoolYearId]").select("option");
for(Element e : options)
{
boolean isDefault = e.hasAttr("selected");
schoolYearOptions.addOption(new Option(e.text(), e.attr("value"), isDefault));
}
currentChoice = new Choice(Arrays.asList(schoolYearOptions.getOptions().get(0).getValue()));
}
/**
* Convert text to a {@link LocalDate} and try to guess the year
* @param text to be parsed
* @return {@link LocalDate} parsed
*/
// private LocalDate parseDate(String text)
// {
// int year = 0, month = 0, day = 0;
// String[] split = text.split("\\.");
// day = Integer.parseInt(split[0]);
// month = Integer.parseInt(split[1]);
// if(currentChoice == null)
// {
// // Pick the current year
// int currYear = LocalDate.now().getYear();
// int currMonth = LocalDate.now().getMonthValue();
// if(month > currMonth && currMonth < 8)
// {
// year = currYear;
// }else if(month < currMonth && currMonth > 8)
// {
// year = currYear + 1;
// }else if(month < currMonth && currMonth < 8)
// {
// year = currYear;
// }
// }else{
// year = Integer.parseInt(currentChoice.getChoices().get(0));
// }
// return LocalDate.of(year, Month.of(month), day);
// }
@Override
public Options[] getOptions() {
return new Options[] {schoolYearOptions};
}
public ArrayList<xyz.thastertyn.Types.AbsenceList> getData()
{
return data;
}
@Override
public String toString()
{
return (!data.isEmpty()) ? data.toString() : null;
}
}

View File

@ -0,0 +1,53 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Options;
public class Canteen extends JecnaScrape {
//public void foo()
//{
// try {
// Document obedy = Jsoup.connect("https://objednavky.jidelnasokolska.cz/faces/secured/month.jsp?terminal=false&keyboard=&printer=")
// .header("Connection", "keep-alive")
// .cookie("XSRF-TOKEN", XSRF_TOKEN)
// .cookie("JSESSIONID", jidelnaJSESSIONID)
// .get();
// System.out.println(obedy.toString());
// //#endregion
// } catch (IOException e) {
// // TO DO Auto-generated catch block
// e.printStackTrace();
// } catch (SecurityException e)
// {
// e.printStackTrace();
// }
//}
@Override
public Options[] getOptions() {
throw new UnsupportedOperationException("Unimplemented method 'getOptions'");
}
@Override
public void download(Choice choice) throws IOException {
throw new UnsupportedOperationException("Unimplemented method 'download'");
}
@Override
public void download() throws IOException {
throw new UnsupportedOperationException("Unimplemented method 'download'");
}
}

View File

@ -1,24 +1,50 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
public class Downloader {
private static String JsessionId;
public static Connection download(String url)
/**
* Provide a general Jsoup Connection for simplicity with some predefined values and JSessionId already set
* @param url
* @return HTML Document downloaded. If it fails, null
*/
public static Document download(String url) throws IOException
{
Connection c = Jsoup.connect(url)
return Jsoup.connect(url)
.header("Connection", "keep-alive")
.cookie("role", "student")
.cookie("JSESSIONID", JsessionId)
.timeout(10000);
return c;
.timeout(10000)
.get();
}
public static void setJSessionId(String JsId)
/**
* Returns a basic {@link Connection} with some predefined headers
* @param url
* @return {@link Connection} with timeout for 10s, and headers needed for downloading from spsejecna.cz
*/
public static Connection getConnection(String url)
{
JsessionId = JsId;
return Jsoup.connect(url)
.header("Connection", "keep-alive")
.cookie("role", "student")
.cookie("JSESSIONID", JsessionId)
.timeout(10000);
}
/**
* Sets JsessionId
* @param newJsessionId JsessionId
*/
public static void setJSessionId(String newJsessionId)
{
JsessionId = newJsessionId;
}
}

View File

@ -0,0 +1,32 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Options;
/**
* Abstract class to wrap around everything that scrapes some data. Most of them also have getData or something similiar,
* but the data types are just so vastly different, they couldnt be wrapped as abstract method and abstract class
*/
public abstract class JecnaScrape {
/**
* returns possible options, like school year or half year
* @return {@link Options} array, for when there are more options available
*/
public abstract Options[] getOptions();
/**
* Downloads the chosen url, given the {@link Choice} which includes all the needed data
* @param choice of any possible data like school year or half year
* @throws IOException like any other download method, in case no internet, timeout, etc.
*/
public abstract void download(Choice choice) throws IOException;
/**
* Downloads the default url, without any parameters
* @throws IOException in case of timeout, no internet, etc.
*/
public abstract void download() throws IOException;
}

View File

@ -1,5 +0,0 @@
package xyz.thastertyn.Scrape;
public class Jidelna {
}

View File

@ -0,0 +1,129 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Pattern;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.FinalMark;
import xyz.thastertyn.Types.Option;
import xyz.thastertyn.Types.Options;
import xyz.thastertyn.Types.Subject;
import xyz.thastertyn.Types.Mark;
public class Marks extends JecnaScrape {
// schoolYear, schoolYearId
private ArrayList<Subject> subjects;
// int znaci id roku, boolean jestli je jen prvni nebo i druhe pololeti
private Options schoolYearOptions;
private Options schoolHalfYearOptions;
public void download() throws IOException
{
download("https://www.spsejecna.cz/score/student");
}
@Override
public void download(Choice choice) throws IOException
{
download(String.format(
"https://www.spsejecna.cz/score/student?schoolYearId=%s&schoolYearHalfId=%s",
choice.getChoices().get(0),
choice.getChoices().get(1)));
}
private void download(String url) throws IOException
{
schoolHalfYearOptions = new Options("Pololeti");
schoolYearOptions = new Options("Skolni R.");
subjects = new ArrayList<>();
Document marksHTMLDocument = Downloader.download(url);
// Subjects stored as <tr>
Elements[] subjectRowsHTML = marksHTMLDocument
.select("table.score")
.select("tr")
.stream()
.map(Element::children)
.toArray(Elements[]::new);
int subjectIndex = 0;
for(int i = 1; i < subjectRowsHTML.length; i++)
{
String fullSubjectName = subjectRowsHTML[i].get(0).text();
// Attempt to shorten the subject name to the string in brackets
String shortSubjectName = Pattern
.compile("\\((.*?)\\)")
.matcher(fullSubjectName)
.results()
.findFirst()
.map(m -> m.group(1))
.orElse(fullSubjectName);
if(subjectRowsHTML[i].get(2).childrenSize() == 0) // Subject doesn't have final mark yet
{
subjects.add(new Subject(shortSubjectName));
}else{
String finalMark = subjectRowsHTML[i].get(2).select("a.scoreFinal").text();
subjects.add(new Subject(shortSubjectName, FinalMark.fromValue(finalMark)));
}
for(Element znamkaElement : subjectRowsHTML[i].get(1).select("a.score"))
{
int mark;
boolean isSmall = false;
String markText = znamkaElement.select("span.value").text();
mark = markText.matches("\\d") ?
Integer.parseInt(markText)
:
-1; // Most likely N (Nehodnocen)
// Small mark will be counted with smaller weight, compared to normal one
isSmall = znamkaElement.hasClass("scoreSmall");
subjects.get(subjectIndex).addMark(new Mark(mark, isSmall, markText));
}
subjectIndex++;
}
Element optionsPanel = marksHTMLDocument.selectFirst("form.listConfigure");
Elements schoolYears = optionsPanel.select("select[id=schoolYearId]").select("option");
Elements halfYears = optionsPanel.select("select[id=schoolYearHalfId]").select("option");
for(Element e : schoolYears)
{
boolean isDefault = e.hasAttr("selected");
schoolYearOptions.addOption(new Option(e.text(), e.attr("value"), isDefault));
}
for(Element e : halfYears)
{
boolean isDefault = e.hasAttr("selected");
schoolHalfYearOptions.addOption(new Option(e.text(), e.attr("value"), isDefault));
}
}
public ArrayList<Subject> getSubjects()
{
return subjects;
}
public Options[] getOptions()
{
return new Options[] {schoolYearOptions, schoolHalfYearOptions};
}
}

View File

@ -1,42 +0,0 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.thastertyn.Tuples.Pair;
public class OmluvnyList {
private ArrayList<Pair<String, String>> data = new ArrayList<>();
public void downloadOmluvnyList() throws UnknownHostException, IOException
{
Document omluvnyListDokumentHTML = Downloader.download("https://www.spsejecna.cz/absence/student").get();
Elements omluvneListy = omluvnyListDokumentHTML.select("table.absence-list").select("tr");
for(Element e : omluvneListy)
{
String date = e.child(0).text();
String text = e.child(1).text();
data.add(new Pair<String, String>(date, text));
}
}
public ArrayList<Pair<String, String>> getData()
{
return data;
}
@Override
public String toString()
{
return (!data.isEmpty()) ? data.toString() : null;
}
}

View File

@ -0,0 +1,50 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Options;
public class Reports extends JecnaScrape {
ArrayList<xyz.thastertyn.Types.Reports> reportList = new ArrayList<>();
public void download() throws UnknownHostException, IOException
{
Document reportDoc = Downloader.download("https://www.spsejecna.cz/user-student/record-list");
Elements report = reportDoc.select("ul.list li");
for(Element e : report)
{
boolean isPositive = false;
String label = "";
Elements spans = e.select("li").select("a.item").select("span");
isPositive = spans.get(0).hasClass("sprite-icon-tick-16");
label = spans.get(1).select("span.label").text();
reportList.add(new xyz.thastertyn.Types.Reports(label, isPositive));
}
}
public ArrayList<xyz.thastertyn.Types.Reports> getSdeleni()
{
return reportList;
}
@Override
public Options[] getOptions() {
return null;
}
@Override
public void download(Choice choice) throws IOException {}
}

View File

@ -1,79 +0,0 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashSet;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
* Jeden radek v rozvrhu
*/
public class Rozvrh {
private String[][] rozvrh = new String[5][10];
/**
* Stahne rozvrh z www.spsejecna.cz a dale ho zpracuje do formy
* se kterou da pracovat
* @param Jsessionid ze stranek
* @throws UnknownHostException kdyz neni pripojeni k internetu
* @throws IOException ostatni exceptiony nejsou dulezite, tak jsou zahrnuty v jednom
*/
public void downloadRozvrh() throws UnknownHostException, IOException
{
Document rozvrhDokumentHTML = Downloader.download("https://www.spsejecna.cz/timetable/class").get();
Elements[] radkyRozvrhuHTML = rozvrhDokumentHTML
.select("table.timetable")
.select("tr")
.stream()
.map(Element::children)
.toArray(Elements[]::new);
for(int i = 1; i < 6; i++)
{
for(int j = 1; j < 11; j++)
{
String predmet = radkyRozvrhuHTML[i].get(j).select("span.subject").text();
// Predmety jako CEL jsou trikrat, staci ale jen jednou
String[] split = predmet.split(" ");
HashSet<String> set = new HashSet<>(Arrays.asList(split));
String pr = String.join("/", set);
rozvrh[i-1][j-1] = pr;
}
}
}
public void setPredmet(String pr, int d, int i)
{
rozvrh[d][i] = pr;
}
public String[][] getRozvrh()
{
return rozvrh;
}
@Override
public String toString()
{
String s = "";
for(String[] st : rozvrh)
{
s += ("| ");
for(String str : st)
{
s += String.format("%-5s", str) + " | ";
}
s += "\n";
}
return s;
}
}

View File

@ -1,46 +0,0 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class Sdeleni {
ArrayList<String> sdeleniList = new ArrayList<>();
public void downloadSdeleni() throws UnknownHostException, IOException
{
Document sdeleniDoc = Downloader.download("https://www.spsejecna.cz/user-student/record-list").get();
Elements sdeleni = sdeleniDoc.select("ul.list li");
for(Element e : sdeleni)
{
boolean isPositive = false;
String label = "";
Elements spans = e.select("li").select("a.item").select("span");
isPositive = spans.get(0).hasClass("sprite-icon-tick-16");
label = spans.get(1).text();
if(isPositive)
{
label = "" + " " + label;
}else{
label = "" + " " + label;
}
sdeleniList.add(label);
}
}
public ArrayList<String> getSdeleni()
{
return sdeleniList;
}
}

View File

@ -0,0 +1,112 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.regex.Pattern;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Option;
import xyz.thastertyn.Types.Options;
public class Timetable extends JecnaScrape {
private xyz.thastertyn.Types.Timetable timetable;
private Options schoolYearOptions;
private Options timetableOptions;
@Override
public void download() throws IOException
{
download("https://www.spsejecna.cz/timetable/class");
}
@Override
public void download(Choice choice) throws IOException {
download(String
.format("https://www.spsejecna.cz/timetable/class?schoolYearId=%s&timetableId=%s",
choice.getChoices().get(0),
choice.getChoices().get(1)));
}
private void download(String url) throws IOException
{
schoolYearOptions = new Options("Skolni R.");
timetableOptions = new Options("Obdobi");
timetable = new xyz.thastertyn.Types.Timetable();
Document timetableHTMLDocument = Downloader.download(url);
Elements[] timetableHTMLRow = timetableHTMLDocument
.select("table.timetable")
.select("tr")
.stream()
.map(Element::children)
.toArray(Elements[]::new);
for(int i = 0; i < 5; i++) // Days
{
for(int j = 0; j < 10; j++) // Individual hours
{
String subject = timetableHTMLRow[i+1].get(j+1).select("span.subject").text();
// Subjects like CEL are thrice, even though everyone has them, make it single
String[] split = subject.split(" ");
HashSet<String> set = new HashSet<>(Arrays.asList(split));
String subj = String.join("/", set);
timetable.get(i).set(j, subj);
}
}
Element optionsPanel = timetableHTMLDocument.selectFirst("form.listConfigure");
Elements schoolYear = optionsPanel.select("select[id=schoolYearId]").select("option");
Elements timetableId = optionsPanel.select("select[id=timetableId]").select("option");
for(Element e : schoolYear)
{
boolean isDefault = e.hasAttr("selected");
schoolYearOptions.addOption(new Option(e.text(), e.attr("value"), isDefault));
}
for(Element e : timetableId)
{
// Try to extract the precise school years to make the text shorter
String optionDisplayText = Pattern
.compile("(Od .* do .*)")
.matcher(e.text())
.results()
.findFirst()
.map(m -> m.group(1))
.orElse(e.text()); // Just use the whole thing if it doesn't follow the pattern
boolean isDefault = e.hasAttr("selected"); // To prevent the last option added be picked as default always
timetableOptions.addOption(new Option(optionDisplayText, e.attr("value"), isDefault));
}
}
public xyz.thastertyn.Types.Timetable getRozvrh()
{
return timetable;
}
@Override
public String toString()
{
return (timetable == null) ?
"Nothing downloaded yet"
:
"All up and ready";
}
@Override
public Options[] getOptions() {
return new Options[] {schoolYearOptions, timetableOptions};
}
}

View File

@ -1,127 +0,0 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.regex.Pattern;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.thastertyn.Tuples.Pair;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Option;
import xyz.thastertyn.Types.Options;
import xyz.thastertyn.Types.Predmet;
import xyz.thastertyn.Types.Znamka;
public class Znamky {
private boolean wasDownloaded;
// schoolYear, schoolYearId
private ArrayList<Predmet> predmety = new ArrayList<>();
// int znaci id roku, boolean jestli je jen prvni nebo i druhe pololeti
private Options schoolYearOptions = new Options("Skolni R.");
private Options schoolHalfYearOptions = new Options("Pololeti");
public void downloadZnamky() throws UnknownHostException, IOException
{
download("https://www.spsejecna.cz/score/student");
}
public void downloadZnamky(Choice choice) throws UnknownHostException, IOException
{
download(String.format(
"https://www.spsejecna.cz/score/student?schoolYearId=%s&schoolYearHalfId=%s",
choice.getChoices().get(0),
choice.getChoices().get(1)));
}
private void download(String url) throws UnknownHostException, IOException
{
//String url =
Document znamkyDokumentHTML = Downloader.download(url).get();
// Predmety ulozene v <tr>
Elements[] radkyPredmetuHTML = znamkyDokumentHTML
.select("table.score")
.select("tr")
.stream()
.map(Element::children)
.toArray(Elements[]::new);
int subjectIndex = 0;
for(int i = 1; i < radkyPredmetuHTML.length; i++)
{
String plnyNazevPredmetu = radkyPredmetuHTML[i].get(0).text();
String jmenoPredmetu = Pattern
.compile("\\((.*?)\\)")
.matcher(plnyNazevPredmetu)
.results()
.findFirst()
.map(m -> m.group(1))
.orElse(plnyNazevPredmetu);
predmety.add(new Predmet(jmenoPredmetu));
for(Element znamkaElement : radkyPredmetuHTML[i].get(1).select("a.score"))
{
int znamka;
boolean jeMala = false;
String textZnamky = znamkaElement.select("span.value").text();
znamka = textZnamky.matches("\\d") ?
Integer.parseInt(textZnamky) :
-1; // Nejspis se jedna o N (Nehodnocen)
// Mala znamka se bude pocitat jako polovicni vaha
jeMala = znamkaElement.hasClass("scoreSmall");
predmety.get(subjectIndex).addZnamka(new Znamka(znamka, jeMala, textZnamky));
}
subjectIndex++;
}
Element optionsPanel = znamkyDokumentHTML.selectFirst("form.listConfigure");
Elements skolniRoky = optionsPanel.select("select[id=schoolYearId]").select("option");
Elements pololeti = optionsPanel.select("select[id=schoolYearHalfId]").select("option");
for(Element e : skolniRoky)
{
schoolYearOptions.addOption(new Option(e.text(), e.attr("value")));
}
for(Element e : pololeti)
{
schoolHalfYearOptions.addOption(new Option(e.text(), e.attr("value")));
}
wasDownloaded = true;
}
public ArrayList<Predmet> getPredmety()
{
return predmety;
}
public Pair<Options, Options> getOptions()
{
return new Pair<Options, Options>(schoolYearOptions, schoolHalfYearOptions);
}
@Override
public String toString()
{
return (wasDownloaded) ?
"All up and ready for use" :
"Nothing downloaded yet";
}
}

View File

@ -1,54 +0,0 @@
package xyz.thastertyn.Tuples;
/**
* Ekvitalent Tuplu, ktery neni zabudovan v jave
*/
public class Pair<T1, T2> {
private T1 value0;
private T2 value1;
public Pair(T1 value0, T2 value1)
{
this.value0 = value0;
this.value1 = value1;
}
public Pair()
{
}
public void put(T1 value0, T2 value1)
{
this.value0 = value0;
this.value1 = value1;
}
public T1 getValue0()
{
return value0;
}
public T2 getValue1()
{
return value1;
}
public void setValue0(T1 value0)
{
this.value0 = value0;
}
public void setValue1(T2 value1)
{
this.value1 = value1;
}
@Override
public String toString()
{
return "[" + value0 + ", " + value1 + "]";
}
}

View File

@ -1,66 +0,0 @@
package xyz.thastertyn.Tuples;
/**
* Ekvitalent Tuplu, ktery neni zabudovan v jave
*/
public class Triplet<T1, T2, T3> {
private T1 value0;
private T2 value1;
private T3 value2;
public Triplet(T1 value0, T2 value1, T3 value2)
{
this.value0 = value0;
this.value1 = value1;
this.value2 = value2;
}
public Triplet()
{
}
public void put(T1 value0, T2 value1, T3 value2)
{
this.value0 = value0;
this.value1 = value1;
this.value2 = value2;
}
public T1 getValue0()
{
return value0;
}
public T2 getValue1()
{
return value1;
}
public T3 getValue2()
{
return value2;
}
public void setValue0(T1 value0)
{
this.value0 = value0;
}
public void setValue1(T2 value1)
{
this.value1 = value1;
}
public void setValue2(T3 value2)
{
this.value2 = value2;
}
@Override
public String toString()
{
return "[" + value0 + ", " + value1 + "," + value2 + "]";
}
}

View File

@ -0,0 +1,31 @@
package xyz.thastertyn.Types;
public class AbsenceList {
private String date;
private String description;
public AbsenceList(String date, String description)
{
this.date = date;
this.description = description;
}
public String getDate() {
return date;
}
public void setDate(String datum) {
this.date = datum;
}
public String getDescription() {
return description;
}
public void setDescription(String popis) {
this.description = popis;
}
}

View File

@ -1,17 +1,20 @@
package xyz.thastertyn.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A choice parsed from dropdowns on the site. Usually contains something like school year or half year id
*/
public class Choice {
private ArrayList<String> choices;
public Choice(String... choices)
private List<String> choices;
public Choice(List<String> c)
{
this.choices.addAll(Arrays.asList(choices));
this.choices = c;
}
public ArrayList<String> getChoices()
public List<String> getChoices()
{
return choices;
}

View File

@ -0,0 +1,23 @@
package xyz.thastertyn.Types;
/**
* Stores username and password
*/
public class Credentials {
private String username;
private String password;
public Credentials(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}

View File

@ -0,0 +1,19 @@
package xyz.thastertyn.Types;
/**
* Stores a single day from the timetable as an array of strings
*/
public class DayOfTimetable {
private String[] subjects = new String[10];
public String get(int index)
{
return subjects[index];
}
public void set(int index, String subject)
{
subjects[index] = subject;
}
}

View File

@ -0,0 +1,75 @@
package xyz.thastertyn.Types;
/**
* Stores the final mark from a subject
*/
public class FinalMark{
public static final FinalMark VYBORNY = new FinalMark(1, "1");
public static final FinalMark CHVALITEBNY = new FinalMark(2, "2");
public static final FinalMark DOBRY = new FinalMark(3, "3");
public static final FinalMark DOSTATECNY = new FinalMark(4, "4");
public static final FinalMark NEDOSTATECNY = new FinalMark(5, "5");
public static final FinalMark NOTHING = new FinalMark(0, "");
public static final FinalMark NEHODNOCEN = new FinalMark(-1, "N");
public static final FinalMark NAPOMENUT_ZA_NEKLASIFIKACI = new FinalMark(-2, "N?");
public static final FinalMark UVOLNEN = new FinalMark(-3, "U");
public static final FinalMark NAPOMENUT_ZA_ZAOSTAVANI = new FinalMark(-4, "5?");
public static final FinalMark UNKNOWN = new FinalMark(-5, "?");
// #94701b
private double value;
private String stringValue;
private FinalMark(double value, String stringValue)
{
this.stringValue = stringValue;
this.value = value;
}
public FinalMark(double value)
{
this.value = value;
this.stringValue = String.format("%.2f", value);
}
public double getValue()
{
return value;
}
@Override
public String toString()
{
return stringValue;
}
public static FinalMark fromValue(String value)
{
if(!value.matches("[0-9]"))
{
return switch (value) {
case "N" -> NEHODNOCEN;
case "N?" -> NAPOMENUT_ZA_NEKLASIFIKACI;
case "5?" -> NAPOMENUT_ZA_ZAOSTAVANI;
case "U" -> UVOLNEN;
default -> UNKNOWN;
};
}else{
int numericValue = Integer.parseInt(value);
return switch (numericValue) {
case 1 -> VYBORNY;
case 2 -> CHVALITEBNY;
case 3 -> DOBRY;
case 4 -> DOSTATECNY;
case 5 -> NEDOSTATECNY;
default -> UNKNOWN;
};
}
}
}

View File

@ -0,0 +1,30 @@
package xyz.thastertyn.Types;
/**
* Wrapper around credentials with the change of having a boolean for whether the user wants to save the credentials or not
*/
public class InputtedCredentials extends Credentials {
private boolean save;
public InputtedCredentials(String username, String password, boolean save)
{
super(username, password);
this.save = save;
}
public InputtedCredentials(Credentials credentials, boolean save)
{
super(credentials.getUsername(), credentials.getPassword());
this.save = save;
}
public Credentials getCredentials()
{
return new Credentials(getUsername(), getPassword());
}
public boolean save() {
return save;
}
}

View File

@ -0,0 +1,34 @@
package xyz.thastertyn.Types;
/**
* A single mark. holds numeric value, weight - small = 1, normal 2
*/
public class Mark {
private int mark;
private String markAsText;
private int weight;
public Mark(int mark, boolean isSmall, String markAsText)
{
this.mark = mark;
this.weight = (isSmall) ? 1 : 2;
this.markAsText = markAsText;
}
public int getMark()
{
return mark;
}
public int getWeight()
{
return weight;
}
@Override
public String toString()
{
return markAsText;
}
}

View File

@ -1,13 +0,0 @@
package xyz.thastertyn.Types;
public class OmluvnyList {
private String datum;
private String popis;
public OmluvnyList(String datum, String popis)
{
this.datum = datum;
this.popis = popis;
}
}

View File

@ -1,27 +1,27 @@
package xyz.thastertyn.Types;
/**
* A single option. Holds a text description, the thing displayed and a string value, the one used in url
*/
public class Option {
private String text;
private String displayText;
private String value;
public Option(String text, String value) {
this.text = text;
private boolean isDefault;
public Option(String text, String value, boolean isDefault) {
this.displayText = text;
this.value = value;
this.isDefault = isDefault;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
public boolean isDefault() {
return isDefault;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return text;
return displayText;
}
}

View File

@ -2,6 +2,9 @@ package xyz.thastertyn.Types;
import java.util.ArrayList;
/**
* Holds all possible a single dropdown on the website can have.
*/
public class Options {
private ArrayList<Option> options = new ArrayList<>();

View File

@ -1,56 +0,0 @@
package xyz.thastertyn.Types;
import java.util.ArrayList;
public class Predmet {
private ArrayList<Znamka> znamky = new ArrayList<>();
private String jmenoPredmetu = "";
private int vyslednaZnamka = 0;
public Predmet(String jmenoPredmetu)
{
this.jmenoPredmetu = jmenoPredmetu;
}
public void addZnamka(Znamka novaZnamka)
{
znamky.add(novaZnamka);
}
public double getPrumer()
{
if(vyslednaZnamka != 0)
{
return vyslednaZnamka;
}
if(znamky.isEmpty())
{
return 0;
}
int celkem = znamky
.stream()
.mapToInt((z) -> z.getZnamka() * z.getVaha())
.sum();
int vahy = znamky
.stream()
.mapToInt((z) -> z.getVaha())
.sum();
return (double) celkem / vahy;
}
public String getJmenoPredmetu()
{
return jmenoPredmetu;
}
public ArrayList<Znamka> getZnamky()
{
return znamky;
}
}

View File

@ -0,0 +1,26 @@
package xyz.thastertyn.Types;
/**
* Reports for parents. Holds a string of text and a boolean for positivity to determine color for display
*/
public class Reports {
private String text;
private boolean isPositive;
public Reports(String text, boolean isPositive)
{
this.text = text;
this.isPositive = isPositive;
}
public boolean isPositive()
{
return isPositive;
}
public String getText()
{
return text;
}
}

View File

@ -0,0 +1,80 @@
package xyz.thastertyn.Types;
import java.util.ArrayList;
/**
* Subject for marks. Holds a name, all its marks and the final mark
*/
public class Subject {
private ArrayList<Mark> marks = new ArrayList<>();
private String subjectName = "";
private boolean isFinal = false;
private FinalMark finalMark = FinalMark.UNKNOWN;
public Subject(String subjectName)
{
this.subjectName = subjectName;
}
public Subject(String subjectName, FinalMark finalMark)
{
this.subjectName = subjectName;
this.finalMark = finalMark;
isFinal = true;
}
public void addMark(Mark newMark)
{
marks.add(newMark);
}
private void calculateFinalMark()
{
if(marks.isEmpty())
{
this.finalMark = FinalMark.NOTHING;
}else{
int total = marks
.stream()
.mapToInt((z) -> z.getMark() * z.getWeight())
.sum();
int weight = marks
.stream()
.mapToInt((z) -> z.getWeight())
.sum();
double finalMark = ((double) total / weight);
this.finalMark = new FinalMark(finalMark);
}
}
public FinalMark getFinalMark()
{
if(!isFinal)
{
calculateFinalMark();
}
return finalMark;
}
public boolean isFinal()
{
return isFinal;
}
public String getSubjectName()
{
return subjectName;
}
public ArrayList<Mark> getMarks()
{
return marks;
}
}

View File

@ -0,0 +1,22 @@
package xyz.thastertyn.Types;
/**
* Whole timetable. holds an array of {@link DayOfTimetable} for each day
*/
public class Timetable {
private DayOfTimetable[] timetable = new DayOfTimetable[5];
public Timetable()
{
for(int i = 0; i < timetable.length; i++)
{
timetable[i] = new DayOfTimetable();
}
}
public DayOfTimetable get(int day)
{
return timetable[day];
}
}

View File

@ -1,30 +0,0 @@
package xyz.thastertyn.Types;
public class Znamka {
private int znamka;
private String znamkaJakoText;
private int vaha;
public Znamka(int znamka, boolean malaZnamka, String znamkaJakoText/*, String text, String datum*/)
{
this.znamka = znamka;
this.vaha = (malaZnamka) ? 1 : 2;
this.znamkaJakoText = znamkaJakoText;
}
public int getZnamka()
{
return znamka;
}
public int getVaha()
{
return vaha;
}
public String getText()
{
return znamkaJakoText;
}
}

View File

@ -0,0 +1,41 @@
package xyz.thastertyn.UserInterface.Content;
import java.util.ArrayList;
import com.googlecode.lanterna.gui2.Direction;
import com.googlecode.lanterna.gui2.GridLayout;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import xyz.thastertyn.UserInterface.Listeners.UpdateListener;
public class AbsenceList extends JecnaContent{
private xyz.thastertyn.Scrape.AbsenceList omluvnyList = new xyz.thastertyn.Scrape.AbsenceList();
public AbsenceList(UpdateListener listener)
{
super(listener);
this.mainPanel = new Panel()
.setLayoutManager(new GridLayout(1));
this.borderLabel = new Label("Omluvny L.");
super.scraper = omluvnyList;
}
@Override
protected void setGUI()
{
this.mainPanel.removeAllComponents();
ArrayList<xyz.thastertyn.Types.AbsenceList> a = omluvnyList.getData();
Panel content = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.addTo(mainPanel);
for(xyz.thastertyn.Types.AbsenceList omluvnyList : a)
{
content.addComponent(new Label(omluvnyList.getDate() + " - " + omluvnyList.getDescription()));
}
}
}

View File

@ -1,5 +1,5 @@
package xyz.thastertyn.UserInterface.Content;
public class Jidelna {
public class Canteen {
}

View File

@ -1,22 +1,113 @@
package xyz.thastertyn.UserInterface.Content;
import java.io.IOException;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import xyz.thastertyn.Scrape.JecnaScrape;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.UserInterface.Dialogs.OptionsDialog;
import xyz.thastertyn.UserInterface.Listeners.UpdateListener;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.Label;
/**
* Wrapper to include all classes that deal with content in some way
*/
public abstract class JecnaContent {
public abstract Panel getPanel();
public abstract void downloadDefault();
protected abstract void download(Choice choice);
public abstract boolean hasStarted();
public abstract Label getLabel();
public abstract void showOptions(final WindowBasedTextGUI textGUI);
protected Panel mainPanel;
protected boolean hasStarted = false;
protected Label borderLabel;
protected UpdateListener listener;
protected JecnaScrape scraper;
/**
* Prepares it's content to be display by gettings the panel from {@link #getPanel()}
*/
protected abstract void setGUI();
public JecnaContent(UpdateListener listener)
{
this.listener = listener;
}
/**
* Call its scraper class to download content using the provided choice
* @param choice
* @throws IOException in case download fails in some way
*/
protected void download(Choice choice) throws IOException
{
if(choice != null)
{
scraper.download(choice);
setGUI();
mainPanel.setPreferredSize(mainPanel.calculatePreferredSize());
listener.updatePanel();
}else{
scraper.download();
setGUI();
}
hasStarted = true;
}
/**
* Tells scraper to download the default content and parse it
* @throws IOException
*/
public void downloadDefault() throws IOException
{
download(null);
}
/**
* Creates a {@link OptionsDialog} to display its options
* @param textGUI where to display the dialog
* @throws IOException upon ok, conent is immediatelly downloaded using {@link #download( choice_from_dialog )}
*/
public void showOptions(WindowBasedTextGUI textGUI) throws IOException {
if(scraper.getOptions() == null)
{
return;
}
OptionsDialog dialog = new OptionsDialog(scraper.getOptions());
Choice choice = dialog.showDialog(textGUI);
if(choice != null)
{
download(choice);
}
}
/**
* Returns the panel where the content should be held and already arranged for display
* @return
*/
public Panel getPanel()
{
return this.mainPanel;
}
/**
* Check if something has been downloaded already
* @return true or false depending on the download state
*/
public boolean hasStarted()
{
return this.hasStarted;
}
/**
* Returns a label with the title text like "Známky", etc.
*/
public Label getLabel()
{
return this.borderLabel;
}
}

View File

@ -0,0 +1,117 @@
package xyz.thastertyn.UserInterface.Content;
import java.util.ArrayList;
import java.util.HashMap;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.TextColor.ANSI;
import com.googlecode.lanterna.graphics.SimpleTheme;
import com.googlecode.lanterna.gui2.Direction;
import com.googlecode.lanterna.gui2.GridLayout;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LayoutData;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import xyz.thastertyn.Types.FinalMark;
import xyz.thastertyn.Types.Subject;
import xyz.thastertyn.Types.Mark;
import xyz.thastertyn.UserInterface.Listeners.UpdateListener;
public class Marks extends JecnaContent {
private final TextColor.RGB VYBORNY = new TextColor.RGB(85,212,0);
private final TextColor.RGB CHVALITEBNY = new TextColor.RGB(196,224,80);
private final TextColor.RGB DOBRY = new TextColor.RGB(255,213,42);
private final TextColor.RGB DOSTATECNY = new TextColor.RGB(255,102,0);
private final TextColor.RGB NEDOSTATECNY = new TextColor.RGB(255,48,48);
private final TextColor.RGB NEHODNOCEN = new TextColor.RGB(0,0,0);
private final LayoutData ALIGN_LEFT = GridLayout.createLayoutData(
GridLayout.Alignment.BEGINNING,
GridLayout.Alignment.BEGINNING,
false,
false);
private final LayoutData ALIGN_RIGHT = GridLayout.createLayoutData(
GridLayout.Alignment.END,
GridLayout.Alignment.BEGINNING,
true,
false);
private xyz.thastertyn.Scrape.Marks marks = new xyz.thastertyn.Scrape.Marks();
public Marks(UpdateListener listener)
{
super(listener);
this.mainPanel = new Panel()
.setLayoutManager(new GridLayout(3));
this.borderLabel = new Label("Znamky");
super.scraper = this.marks;
}
@Override
protected void setGUI()
{
mainPanel.removeAllComponents();
ArrayList<Subject> subjects = marks.getSubjects();
// Colors are used a lot and instead of referencing them in lengthy lines, this seems shorter
HashMap<Integer, SimpleTheme> colors = new HashMap<>();
colors.put(1, new SimpleTheme(ANSI.BLACK, VYBORNY));
colors.put(2, new SimpleTheme(ANSI.BLACK, CHVALITEBNY));
colors.put(3, new SimpleTheme(ANSI.BLACK, DOBRY));
colors.put(4, new SimpleTheme(ANSI.BLACK, DOSTATECNY));
colors.put(5, new SimpleTheme(ANSI.BLACK, NEDOSTATECNY));
colors.put(-1, new SimpleTheme(ANSI.WHITE, NEHODNOCEN));
colors.put(-2, new SimpleTheme(ANSI.WHITE, NEHODNOCEN));
// Column for subject names
Panel subjectNames = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.setLayoutData(ALIGN_LEFT)
.addTo(mainPanel);
// Column for marks
Panel marks = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.setLayoutData(ALIGN_LEFT)
.addTo(mainPanel);
// Column for final mark
Panel finalMarks = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.setLayoutData(ALIGN_RIGHT)
.addTo(mainPanel);
for(Subject subject : subjects)
{
Panel individualMarks = new Panel()
.setLayoutManager(new LinearLayout(Direction.HORIZONTAL))
.addTo(marks);
if(subject.getMarks().isEmpty())
{
individualMarks.addComponent(new Label(""));
}
for(Mark mark : subject.getMarks())
{
Label markLabel = new Label(mark.toString());
markLabel.setTheme(colors.get(mark.getMark()));
individualMarks.addComponent(markLabel);
}
FinalMark finalMark = subject.getFinalMark();
Label vysl = new Label(finalMark.toString());
vysl.setTheme(colors.get((int) Math.round(finalMark.getValue())));
subjectNames.addComponent(new Label(subject.getSubjectName()));
finalMarks.addComponent(vysl);
}
}
}

View File

@ -1,76 +0,0 @@
package xyz.thastertyn.UserInterface.Content;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import xyz.thastertyn.Tuples.Pair;
import xyz.thastertyn.Types.Choice;
public class OmluvnyList extends JecnaContent{
private Panel omluvnyPanel = new Panel();
private Label borderLabel = new Label("Omluvny L.");
private xyz.thastertyn.Scrape.OmluvnyList omluvnyList = new xyz.thastertyn.Scrape.OmluvnyList();
private boolean hasStarted = false;
@Override
public void downloadDefault()
{
download(null);
}
@Override
protected void download(Choice choice)
{
try{
omluvnyList.downloadOmluvnyList();
setGUI();
hasStarted = true;
}catch(UnknownHostException e)
{
// omluvnyPanel.addComponent(new Label("A connection error occurred"));
}catch(IOException e)
{
// omluvnyPanel.addComponent(new Label("An error occurred"));
}
}
@Override
protected void setGUI()
{
ArrayList<Pair<String, String>> a = omluvnyList.getData();
for(Pair<String, String> p : a)
{
omluvnyPanel.addComponent(new Label(p.getValue0() + " - " + p.getValue1()));
}
}
@Override
public Panel getPanel() {
return omluvnyPanel;
}
@Override
public boolean hasStarted() {
return hasStarted;
}
@Override
public Label getLabel() {
return borderLabel;
}
@Override
public void showOptions(WindowBasedTextGUI textGUI) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getOptions'");
}
}

View File

@ -0,0 +1,56 @@
package xyz.thastertyn.UserInterface.Content;
import java.util.ArrayList;
import com.googlecode.lanterna.TextColor.ANSI;
import com.googlecode.lanterna.gui2.Direction;
import com.googlecode.lanterna.gui2.GridLayout;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import xyz.thastertyn.UserInterface.Listeners.UpdateListener;
public class Reports extends JecnaContent {
private xyz.thastertyn.Scrape.Reports reports = new xyz.thastertyn.Scrape.Reports();
public Reports(UpdateListener listener)
{
super(listener);
this.mainPanel = new Panel().setLayoutManager(new GridLayout(1)
.setLeftMarginSize(1)
.setRightMarginSize(1));
this.borderLabel = new Label("Sdeleni R.");
super.scraper = this.reports;
}
@Override
protected void setGUI()
{
mainPanel.removeAllComponents();
ArrayList<xyz.thastertyn.Types.Reports> reportsList = reports.getSdeleni();
for(xyz.thastertyn.Types.Reports reports : reportsList)
{
Panel row = new Panel().setLayoutManager(new LinearLayout(Direction.HORIZONTAL));
Label checkmark = new Label("");
Label text = new Label(reports.getText());
if(reports.isPositive())
{
checkmark.setForegroundColor(ANSI.GREEN);
checkmark.setText("");
}else{
checkmark.setForegroundColor(ANSI.RED);
checkmark.setText("");
}
row.addComponent(checkmark)
.addComponent(text)
.addTo(mainPanel);
}
}
}

View File

@ -1,94 +0,0 @@
package xyz.thastertyn.UserInterface.Content;
import java.io.IOException;
import java.net.UnknownHostException;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import com.googlecode.lanterna.gui2.table.Table;
import xyz.thastertyn.Types.Choice;
public class Rozvrh extends JecnaContent {
private Panel rozvrhPanel = new Panel();
private Label borderLabel = new Label("Rozvrh");
private xyz.thastertyn.Scrape.Rozvrh rozvrh = new xyz.thastertyn.Scrape.Rozvrh();
private boolean hasStarted = false;
private String[] labels = {"Den", "7:30-8:15", "8:25-9:10", "9:20-10:05", "10:20-11:05", "11:15-12:00", "12:10-12:55", "13:05-13:50", "14:00-14:45", "14:55-15:40", "15:50-16:35"};
//private String[] labels = {"Den", "1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.", "9.", "10."};
String[] daysLabels = {"PO", "UT", "ST", "CT", "PA"};
Table<String> t = new Table<>(labels);
@Override
public void downloadDefault()
{
download(null);
}
@Override
protected void download(Choice choice)
{
try{
rozvrh.downloadRozvrh();
setGUI();
hasStarted = true;
}catch(UnknownHostException e)
{
rozvrhPanel.addComponent(new Label("A connection error occurred"));
}catch(IOException e)
{
rozvrhPanel.addComponent(new Label("An error occurred"));
}
}
@Override
protected void setGUI()
{
String[][] rozvrhArray = rozvrh.getRozvrh();
String[] den = new String[11];
for(int i = 0; i < rozvrhArray.length; i++)
{
den[0] = daysLabels[i];
for(int j = 1; j < rozvrhArray[i].length + 1; j++)
{
den[j] = (rozvrhArray[i][j - 1].isBlank()) ? " - " : rozvrhArray[i][j - 1];
}
t.getTableModel().addRow(den);
}
rozvrhPanel.addComponent(t);
}
@Override
public boolean hasStarted()
{
return hasStarted;
}
@Override
public Panel getPanel()
{
return rozvrhPanel;
}
@Override
public Label getLabel()
{
return borderLabel;
}
@Override
public void showOptions(WindowBasedTextGUI textGUI) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getOptions'");
}
}

View File

@ -1,81 +0,0 @@
package xyz.thastertyn.UserInterface.Content;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import com.googlecode.lanterna.gui2.GridLayout;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import xyz.thastertyn.Types.Choice;
public class Sdeleni extends JecnaContent {
private Panel sdeleniPanel = new Panel();
private Label borderLabel = new Label("Sdeleni R.");
private xyz.thastertyn.Scrape.Sdeleni sdeleni = new xyz.thastertyn.Scrape.Sdeleni();
private boolean hasStarted = false;
@Override
public void downloadDefault()
{
download(null);
}
@Override
protected void download(Choice choice)
{
try{
sdeleni.downloadSdeleni();
hasStarted = true;
}catch(UnknownHostException e)
{
}catch(IOException e)
{
}
}
@Override
protected void setGUI()
{
sdeleniPanel.setLayoutManager(new GridLayout(1)
.setLeftMarginSize(1)
.setRightMarginSize(1));
ArrayList<String> sdeleniList = sdeleni.getSdeleni();
for(String s : sdeleniList)
{
sdeleniPanel.addComponent(new Label(s));
}
}
@Override
public Label getLabel() {
return borderLabel;
}
@Override
public Panel getPanel() {
return sdeleniPanel;
}
@Override
public boolean hasStarted() {
return hasStarted;
}
@Override
public void showOptions(WindowBasedTextGUI textGUI) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getOptions'");
}
}

View File

@ -0,0 +1,58 @@
package xyz.thastertyn.UserInterface.Content;
import java.util.ArrayList;
import java.util.List;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.table.Table;
import xyz.thastertyn.UserInterface.Listeners.UpdateListener;
public class Timetable extends JecnaContent {
private xyz.thastertyn.Scrape.Timetable rozvrh = new xyz.thastertyn.Scrape.Timetable();
private String[] labels = {"Den", "7:30-8:15", "8:25-9:10", "9:20-10:05", "10:20-11:05", "11:15-12:00", "12:10-12:55", "13:05-13:50", "14:00-14:45", "14:55-15:40", "15:50-16:35"};
//private String[] labels = {"Den", "1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.", "9.", "10."};
String[] daysLabels = {"PO", "UT", "ST", "CT", "PA"};
Table<String> table;
public Timetable(UpdateListener listener)
{
super(listener);
this.mainPanel = new Panel();
this.borderLabel = new Label("Rozvrh");
super.scraper = this.rozvrh;
}
@Override
protected void setGUI()
{
mainPanel.removeAllComponents();
table = new Table<>(labels);
xyz.thastertyn.Types.Timetable timetable = rozvrh.getRozvrh();
for(int day = 0; day < 5; day++)
{
List<String> currentRow = new ArrayList<>();
// Set the day (Po, Ut, St,...)
currentRow.add(daysLabels[day]);
// Add the classes
for(int hour = 0; hour < 10; hour++)
{
currentRow.add((timetable.get(day).get(hour).isBlank()) ?
" - " // Empty class, nothing taught at that moment
:
timetable.get(day).get(hour)); // Get class
}
table.getTableModel().addRow(currentRow);
}
mainPanel.addComponent(table);
}
}

View File

@ -1,168 +0,0 @@
package xyz.thastertyn.UserInterface.Content;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.TextColor.ANSI;
import com.googlecode.lanterna.graphics.SimpleTheme;
import com.googlecode.lanterna.gui2.Direction;
import com.googlecode.lanterna.gui2.GridLayout;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LayoutData;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Predmet;
import xyz.thastertyn.Types.Znamka;
import xyz.thastertyn.UserInterface.Dialogs.OptionsDialog;
public class Znamky extends JecnaContent {
private final TextColor.RGB VYBORNY = new TextColor.RGB(85,212,0);
private final TextColor.RGB CHVALITEBNY = new TextColor.RGB(196,224,80);
private final TextColor.RGB DOBRY = new TextColor.RGB(255,213,42);
private final TextColor.RGB DOSTATECNY = new TextColor.RGB(255,102,0);
private final TextColor.RGB NEDOSTATECNY = new TextColor.RGB(255,48,48);
private final TextColor.RGB NEHODNOCEN = new TextColor.RGB(0,0,0);
private final LayoutData ALIGN_LEFT = GridLayout.createLayoutData(
GridLayout.Alignment.BEGINNING,
GridLayout.Alignment.BEGINNING,
false,
false);
private final LayoutData ALIGN_RIGHT = GridLayout.createLayoutData(
GridLayout.Alignment.END,
GridLayout.Alignment.BEGINNING,
true,
false);
private Panel mainPanel = new Panel()
.setLayoutManager(new GridLayout(3));
private Label borderLabel = new Label("Znamky");
private xyz.thastertyn.Scrape.Znamky znamky = new xyz.thastertyn.Scrape.Znamky();
private boolean hasStarted = false;
@Override
public void downloadDefault()
{
download(null);
}
@Override
protected void download(Choice choice)
{
try{
if(choice != null)
{
znamky.downloadZnamky(choice);
}else{
znamky.downloadZnamky();
}
setGUI();
hasStarted = true;
}catch(IOException e)
{
mainPanel.addComponent(new Label("An error has occured"));
}
}
@Override
protected void setGUI()
{
ArrayList<Predmet> predmety = znamky.getPredmety();
HashMap<Integer, SimpleTheme> barvy = new HashMap<>();
barvy.put(1, new SimpleTheme(ANSI.BLACK, VYBORNY));
barvy.put(2, new SimpleTheme(ANSI.BLACK, CHVALITEBNY));
barvy.put(3, new SimpleTheme(ANSI.BLACK, DOBRY));
barvy.put(4, new SimpleTheme(ANSI.BLACK, DOSTATECNY));
barvy.put(5, new SimpleTheme(ANSI.BLACK, NEDOSTATECNY));
barvy.put(-1, new SimpleTheme(ANSI.WHITE, NEHODNOCEN));
// Sloupec pro jmena predmetu
Panel jmemaPredmetu = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.setLayoutData(ALIGN_LEFT)
.addTo(mainPanel);
// Sloupec pro znamky z predmetu
Panel znamky = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.setLayoutData(ALIGN_LEFT)
.addTo(mainPanel);
// Sloupec pro vyslednou znamku
Panel vysledneZnamky = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.setLayoutData(ALIGN_RIGHT)
.addTo(mainPanel);
for(Predmet predmet : predmety)
{
Panel jednotliveZnamky = new Panel()
.setLayoutManager(new LinearLayout(Direction.HORIZONTAL))
.addTo(znamky);
if(predmet.getZnamky().isEmpty())
{
jednotliveZnamky.addComponent(new Label(""));
}
for(Znamka znamka : predmet.getZnamky())
{
Label znamkaLabel = new Label(znamka.getText());
znamkaLabel.setTheme(barvy.get(znamka.getZnamka()));
jednotliveZnamky.addComponent(znamkaLabel);
}
double prumer = predmet.getPrumer();
Label vysl = new Label(String.format("%.2f", prumer));
vysl.setTheme(barvy.get((int) Math.round(prumer)));
jmemaPredmetu.addComponent(new Label(predmet.getJmenoPredmetu()));
vysledneZnamky.addComponent(vysl);
}
}
@Override
public Panel getPanel()
{
return mainPanel;
}
@Override
public boolean hasStarted()
{
return hasStarted;
}
@Override
public Label getLabel()
{
return borderLabel;
}
@Override
public void showOptions(WindowBasedTextGUI textGUI) {
OptionsDialog d = new OptionsDialog(znamky.getOptions().getValue0(), znamky.getOptions().getValue1());
Choice c = d.showDialog(textGUI);
if(c != null)
{
download(c);
}
}
}

View File

@ -0,0 +1,80 @@
package xyz.thastertyn.UserInterface.Dialogs;
import java.util.Arrays;
import com.googlecode.lanterna.gui2.Button;
import com.googlecode.lanterna.gui2.Direction;
import com.googlecode.lanterna.gui2.GridLayout;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.Window;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import com.googlecode.lanterna.gui2.dialogs.DialogWindow;
import xyz.thastertyn.Login.LocalCredentials;
import xyz.thastertyn.Login.LoginController;
import xyz.thastertyn.UserInterface.Listeners.ContentResetListener;
public class EscapeDialog extends DialogWindow {
private WindowBasedTextGUI textGUI;
private ContentResetListener resetListener;
public EscapeDialog(WindowBasedTextGUI textGUI, ContentResetListener resetListener)
{
super("Escape Menu");
this.textGUI = textGUI;
this.resetListener = resetListener;
Panel mainPanel = new Panel()
.setLayoutManager(new GridLayout(1)
.setLeftMarginSize(1)
.setRightMarginSize(1));
new Panel()
.setLayoutManager(
new LinearLayout(Direction.VERTICAL))
.addComponent(new Button("Return", this::onReturn))
.addComponent(new Button("Logout", this::onLogout))
.addComponent(new Button("Delete stored login", this::onDeleteLogin))
.addComponent(new Button("Exit", this::onExit))
.addTo(mainPanel);
setHints(Arrays.asList(Window.Hint.CENTERED));
setComponent(mainPanel);
}
public void onReturn()
{
close();
}
public void onDeleteLogin()
{
close();
LocalCredentials.getInstance().deleteCredentials();
LoginController controller = new LoginController(textGUI);
controller.loginUsingGui();
resetListener.reset();
}
public void onLogout()
{
close();
LoginController controller = new LoginController(textGUI);
controller.login(true);
resetListener.reset();
}
public void onExit()
{
close();
System.exit(0);
}
@Override
public Object showDialog(WindowBasedTextGUI textGUI) {
super.showDialog(textGUI);
return null;
}
}

View File

@ -3,7 +3,7 @@ package xyz.thastertyn.UserInterface.Dialogs;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import com.googlecode.lanterna.gui2.Button;
import com.googlecode.lanterna.gui2.ComboBox;
@ -25,7 +25,7 @@ public class OptionsDialog extends DialogWindow {
private boolean useData = false;
public OptionsDialog(Options... options)
public OptionsDialog(Options[] options)
{
super("Choose from below");
@ -36,6 +36,17 @@ public class OptionsDialog extends DialogWindow {
boxs.add(new ComboBox<Option>(o.getOptions()));
}
for(int i = 0; i < options.length; i++)
{
for(int j = 0; j < options[i].getOptions().size(); j++)
{
if(options[i].getOptions().get(j).isDefault())
{
boxs.get(i).setSelectedIndex(j);
}
}
}
Panel mainPanel = new Panel()
.setLayoutManager(new GridLayout(1)
.setLeftMarginSize(1)
@ -91,11 +102,13 @@ public class OptionsDialog extends DialogWindow {
super.showDialog(textGUI);
return (useData) ?
new Choice( // User pressed ok
boxs.stream()
.map(b -> b.getSelectedItem().toString())
.toArray(String[]::new))
// User pressed Ok
new Choice(boxs.stream()
.map(box -> box.getSelectedItem().getValue())
.collect(Collectors.toList()))
// Why did .toList() stop working?
:
null; // User pressed Cancel
// User pressed Cancel
null;
}
}

View File

@ -0,0 +1,8 @@
package xyz.thastertyn.UserInterface.Listeners;
/**
* Interface for when accounts have been switched and a complete wipe of content is needed
*/
public interface ContentResetListener {
public void reset();
}

View File

@ -0,0 +1,8 @@
package xyz.thastertyn.UserInterface.Listeners;
public interface UpdateListener {
/**
* Redraw the screen when panel changes due to Choice being used
*/
public void updatePanel();
}

View File

@ -0,0 +1,236 @@
package xyz.thastertyn.UserInterface.Listeners;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.gui2.Borders;
import com.googlecode.lanterna.gui2.Direction;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.Window;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import com.googlecode.lanterna.gui2.WindowListener;
import com.googlecode.lanterna.gui2.dialogs.MessageDialog;
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton;
import com.googlecode.lanterna.input.KeyStroke;
import com.googlecode.lanterna.input.KeyType;
import xyz.thastertyn.UserInterface.Content.JecnaContent;
import xyz.thastertyn.UserInterface.Content.AbsenceList;
import xyz.thastertyn.UserInterface.Content.Timetable;
import xyz.thastertyn.UserInterface.Content.Reports;
import xyz.thastertyn.UserInterface.Content.Marks;
import xyz.thastertyn.UserInterface.Dialogs.EscapeDialog;
/**
* Holds all the content displayables and moves between them when needed on tab, or shift tab press
*/
// The biggest and greatest.
public class WindowSwitchListener implements WindowListener, UpdateListener, ContentResetListener {
private WindowBasedTextGUI textGUI;
private JecnaContent[] contents = {
new Timetable(this),
new Marks(this), // This being first doesn't resize properly for some reason
new Reports(this),
new AbsenceList(this)
};
private Label[] tabs = new Label[contents.length];
private Panel tabsPanel = new Panel()
.setLayoutManager(new LinearLayout(Direction.HORIZONTAL));
private Panel holderPanel = new Panel();
int current = contents.length - 1;
public WindowSwitchListener(Panel holder, WindowBasedTextGUI textGUI)
{
this.textGUI = textGUI;
holder.addComponent(tabsPanel);
holder.addComponent(holderPanel.withBorder(Borders.singleLine()));
for(int i = 0; i < contents.length; i++)
{
this.tabs[i] = contents[i].getLabel();
tabsPanel.addComponent(this.tabs[i]);
}
next();
}
private void next()
{
if(current + 1 == contents.length)
{
current = 0;
}else{
current++;
}
loadPanel();
}
private void previous()
{
if(current - 1 == -1)
{
current = contents.length - 1;
}else{
current--;
}
loadPanel();
}
private void loadPanel()
{
checkIfDownloaded();
holderPanel.removeAllComponents();
tabsPanel.removeAllComponents();
setPanelPreferedSize();
updateLabels();
}
private void checkIfDownloaded()
{
if(!contents[current].hasStarted())
{
try{
contents[current].downloadDefault();
}catch(IOException e)
{
MessageDialog.showMessageDialog(textGUI, "Something failed", e.getMessage(), MessageDialogButton.OK);
}
}
}
/**
* If a panel is smaller than the bar with labels, make it at least the bar size
*/
private void setPanelPreferedSize()
{
contents[current].getPanel().setSize(contents[current].getPanel().calculatePreferredSize()); // Fix for reports not being big enough
int labelTabColumnSize = tabsPanel
.getSize()
.getColumns();
int currentPanelColumnSize = contents[current]
.getPanel()
.getSize()
.getColumns();
if(currentPanelColumnSize < labelTabColumnSize)
{
int currentPanelRowSize = contents[current].getPanel().getPreferredSize().getRows();
holderPanel.addComponent(
contents[current]
.getPanel()
.setPreferredSize(new TerminalSize(
labelTabColumnSize,
currentPanelRowSize))); // Pretty janky, but basically just create a new TerminalSize with height the same and the width of label bar
return;
}
holderPanel.addComponent(contents[current].getPanel());
}
/**
* Gets all labels again and properly sets their borders to single or double depending on {@link #current}
*/
private void updateLabels()
{
for(int i = 0; i < tabs.length; i++)
{
if(i == current)
{
tabsPanel.addComponent(tabs[i].withBorder(Borders.doubleLine()));
}else{
tabsPanel.addComponent(tabs[i].withBorder(Borders.singleLine()));
}
}
}
/**
* Calls the {@link #current}'s showOptions with a textGui
*/
private void showOptions()
{
try{
contents[current].showOptions(textGUI);
}catch(IOException e)
{
MessageDialog.showMessageDialog(textGUI, "Something failed", "An error with connection has occured, either no connection at all, or connection timed out", MessageDialogButton.OK);
}
}
@Override
public void onInput(Window basePane, KeyStroke keyStroke, AtomicBoolean deliverEvent) {
KeyType type = keyStroke.getKeyType();
switch(type)
{
case Tab:
next();
break;
case ReverseTab: // Shift + Tab
previous();
break;
case Character:
if(keyStroke.getCharacter() == ' ')
{
showOptions();
}
break;
case Escape:
EscapeDialog dialog = new EscapeDialog(textGUI, this);
dialog.showDialog(textGUI);
break;
default:
break;
}
}
@Override
public void onUnhandledInput(Window basePane, KeyStroke keyStroke, AtomicBoolean hasBeenHandled) {
hasBeenHandled.set(true);
}
@Override
public void onResized(Window window, TerminalSize oldSize, TerminalSize newSize) {
window.invalidate();
}
@Override
public void onMoved(Window window, TerminalPosition oldPosition, TerminalPosition newPosition) {
window.invalidate();
}
@Override
public void updatePanel() {
holderPanel.removeAllComponents();
setPanelPreferedSize();
holderPanel.addComponent(contents[current].getPanel());
}
/**
* On user change should wipe everything
*/
@Override
public void reset()
{
contents = new JecnaContent[] {
new Timetable(this),
new Marks(this),
new Reports(this),
new AbsenceList(this)
};
loadPanel();
}
}

View File

@ -19,6 +19,7 @@ import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
import com.googlecode.lanterna.terminal.Terminal;
import xyz.thastertyn.Login.LoginController;
import xyz.thastertyn.UserInterface.Listeners.WindowSwitchListener;
public class MainWindow {
@ -27,7 +28,14 @@ public class MainWindow {
private Window window;
private MultiWindowTextGUI textGUI;
/**
* Create:
* <ul>
* <li> All the main components like Terminal, and Screen </li>
* <li> {@link LoginController} and try logging in using any of its methods </li>
* <li> {@link WindowSwitchListener} for the purpose of switching between tabs using {@code Tab} and {@code ReverseTab} (Shift + Tab) </li>
* </ul>
*/
public void run()
{
try {
@ -37,7 +45,7 @@ public class MainWindow {
screen = new TerminalScreen(terminal);
screen.startScreen();
// Create window to hold the panel
window = new BasicWindow();
window.setHints(Arrays.asList(Window.Hint.CENTERED));
@ -46,25 +54,25 @@ public class MainWindow {
textGUI = new MultiWindowTextGUI(screen, new DefaultWindowManager(), new EmptySpace(TextColor.ANSI.BLACK_BRIGHT));
//#endregion
// Create panel to hold components
// Create panel to hold all components
final Panel mainPanel = new Panel();
mainPanel.setLayoutManager(new LinearLayout(Direction.VERTICAL));
window.setComponent(mainPanel);
String title = "Jecnak";
// Create a secondary panel, as working with the mainPanel is quite buggy
Panel content = new Panel();
mainPanel.addComponent(content.withBorder(Borders.singleLine(title)));
mainPanel.addComponent(content.withBorder(Borders.singleLine("Jecnak")));
// Try logging in using any method
LoginController controller = new LoginController(textGUI);
controller.login();
controller.login(false);
window.addWindowListener(new WindowSwitchListener(content, title, textGUI));
// Create a WindowListener for tab and shift tab for moving between tabs
window.addWindowListener(new WindowSwitchListener(content, textGUI));
textGUI.addWindowAndWait(window);
} catch (IOException e) {
e.printStackTrace();
}

View File

@ -1,144 +0,0 @@
package xyz.thastertyn.UserInterface;
import java.util.concurrent.atomic.AtomicBoolean;
import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.gui2.Borders;
import com.googlecode.lanterna.gui2.Direction;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.Window;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import com.googlecode.lanterna.gui2.WindowListener;
import com.googlecode.lanterna.gui2.dialogs.MessageDialog;
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton;
import com.googlecode.lanterna.input.KeyStroke;
import com.googlecode.lanterna.input.KeyType;
import xyz.thastertyn.UserInterface.Content.JecnaContent;
import xyz.thastertyn.UserInterface.Content.OmluvnyList;
import xyz.thastertyn.UserInterface.Content.Rozvrh;
import xyz.thastertyn.UserInterface.Content.Sdeleni;
import xyz.thastertyn.UserInterface.Content.Znamky;
public class WindowSwitchListener implements WindowListener {
private WindowBasedTextGUI textGUI;
private JecnaContent[] contents = {
new Rozvrh(),
new Znamky(),
new Sdeleni(),
new OmluvnyList()
};
private Label[] tabs = new Label[contents.length];
private Panel tabsPanel = new Panel().setLayoutManager(new LinearLayout(Direction.HORIZONTAL));
private Panel holderPanel = new Panel();
private Panel errorPanel = new Panel().addComponent(new Label("Something went wrong"));
int current = contents.length - 1;
public WindowSwitchListener(Panel holder, String title, WindowBasedTextGUI textGUI)
{
this.textGUI = textGUI;
holder.addComponent(tabsPanel);
holder.addComponent(holderPanel.withBorder(Borders.singleLine()));
for(int i = 0; i < contents.length; i++)
{
this.tabs[i] = contents[i].getLabel();
tabsPanel.addComponent(this.tabs[i]);
}
next();
}
private void next()
{
if(current + 1 == contents.length)
{
current = 0;
}else{
current++;
}
if(!contents[current].hasStarted())
{
contents[current].downloadDefault();
}
holderPanel.removeAllComponents();
tabsPanel.removeAllComponents();
if(contents[current].hasStarted())
{
int tabColumns = tabsPanel.getSize().getColumns();
if(contents[current].getPanel().getSize().getColumns() < tabColumns)
{
holderPanel.addComponent(
contents[current].getPanel().setPreferredSize(new TerminalSize(tabColumns,
contents[current].getPanel().getPreferredSize().getRows())));
}else{
holderPanel.addComponent(contents[current].getPanel());
}
}else{
holderPanel.addComponent(errorPanel);
}
for(int i = 0; i < tabs.length; i++)
{
if(i == current)
{
tabsPanel.addComponent(tabs[i].withBorder(Borders.doubleLine()));
}else{
tabsPanel.addComponent(tabs[i].withBorder(Borders.singleLine()));
}
}
}
@Override
public void onInput(Window basePane, KeyStroke keyStroke, AtomicBoolean deliverEvent) {
KeyType type = keyStroke.getKeyType();
switch(type)
{
case Tab:
next();
break;
case Character:
if(keyStroke.getCharacter() == ' ')
{
contents[current].showOptions(textGUI);
}
break;
case Escape:
MessageDialog.showMessageDialog(textGUI, "Options", "Loging out and some other stuff soon", MessageDialogButton.OK);
break;
default:
break;
}
}
@Override
public void onUnhandledInput(Window basePane, KeyStroke keyStroke, AtomicBoolean hasBeenHandled) {
// TODO Auto-generated method stub
}
@Override
public void onResized(Window window, TerminalSize oldSize, TerminalSize newSize) {
window.invalidate();
}
@Override
public void onMoved(Window window, TerminalPosition oldPosition, TerminalPosition newPosition) {
// TODO Auto-generated method stub
}
}

View File

@ -1,39 +1,73 @@
package xyz.thastertyn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import xyz.thastertyn.Types.FinalMark;
import xyz.thastertyn.Types.Mark;
import xyz.thastertyn.Types.Subject;
/**
* Unit test for simple App.
*/
public class AppTest
{
/**
* Rigorous Test :-)
*/
@Test
public void shouldAnswerWithTrue()
{
assertTrue( true );
}
/**
* Rigorous Test :-)
*/
@Test
public void shouldAnswerWithTrue()
{
assertTrue( true );
}
@Test
public void hasJavaAbove11()
{
// https://stackoverflow.com/questions/2591083/getting-java-version-at-runtime#2591122
String version = System.getProperty("java.version");
int v;
@Test
public void noMarksPresentTest()
{
// No marks are present on start
Subject subject = new Subject("Test Subject");
if(version.startsWith("1."))
{
version = version.substring(2, 3);
}else {
int dot = version.indexOf(".");
if(dot != -1) { version = version.substring(0, dot); }
}
v = Integer.parseInt(version);
assertTrue(subject.getMarks().isEmpty());
}
assertTrue("Java >= 11 is required", (v >= 11));
}
@Test
public void correctAverage()
{
Subject subject = new Subject("Test");
// Average is calculated properly
subject.addMark(new Mark(1, false, "1"));
subject.addMark(new Mark(3, false, "3"));
assertTrue(subject.getFinalMark().getValue() == 2.0);
}
@Test
public void correctSmallMarkAverage()
{
// Small marks are calculated properly
Subject subject = new Subject("Test");
subject.addMark(new Mark(1, false, "1"));
subject.addMark(new Mark(3, false, "3"));
subject.addMark(new Mark(5, true, "5"));
subject.addMark(new Mark(5, true, "5"));
assertTrue(subject.getFinalMark().getValue() == 3.0);
}
@Test
public void marksDontChangeFinalMark()
{
// Once a final mark is given, no additional marks should change it
Subject subject = new Subject("Test", FinalMark.NEDOSTATECNY);
subject.addMark(new Mark(1, false, "a"));
subject.addMark(new Mark(1, false, "a"));
subject.addMark(new Mark(1, false, "a"));
subject.addMark(new Mark(1, false, "a"));
assertEquals(FinalMark.NEDOSTATECNY, subject.getFinalMark());
}
}