├── .gitignore
├── .gitlab-ci.yml
├── JobApplicationBot
└── src
│ ├── main
│ ├── app
│ │ └── net
│ │ │ └── codejava
│ │ │ └── swing
│ │ │ ├── BotGUI.java
│ │ │ ├── CharLengthDocumentListener.java
│ │ │ ├── CreateGUIComponents.java
│ │ │ ├── DocumentSizeFilter.java
│ │ │ ├── GUIComponentsHelper.java
│ │ │ ├── GlassdoorPanel.java
│ │ │ ├── IndeedPanel.java
│ │ │ ├── LeverGreenhousePanel.java
│ │ │ ├── LinkedInPanel.java
│ │ │ ├── MessageDialog.java
│ │ │ ├── RunGlassdoorBot.java
│ │ │ ├── RunIndeedBot.java
│ │ │ ├── RunLeverGreenhouseBot.java
│ │ │ ├── RunLinkedInBot.java
│ │ │ ├── SingletonTab.java
│ │ │ ├── TextFieldListener.java
│ │ │ └── Validator.java
│ ├── java
│ │ └── com
│ │ │ └── btieu
│ │ │ └── JobApplicationBot
│ │ │ ├── ApplyInterface.java
│ │ │ ├── Bot.java
│ │ │ ├── GlassdoorApplyBot.java
│ │ │ ├── GlassdoorBot.java
│ │ │ ├── GreenhouseForms.java
│ │ │ ├── IndeedApplyBot.java
│ │ │ ├── IndeedBot.java
│ │ │ ├── JobApplicationData.java
│ │ │ ├── JobIterator.java
│ │ │ ├── JobPostingData.java
│ │ │ ├── LeverForms.java
│ │ │ ├── LeverGreenhouseBot.java
│ │ │ ├── LinkedInBot.java
│ │ │ ├── LinkedInPerson.java
│ │ │ ├── Pagination.java
│ │ │ ├── PagingInterface.java
│ │ │ ├── SingletonDriver.java
│ │ │ └── WriteFiles.java
│ └── parser
│ │ └── com
│ │ └── btieu
│ │ └── JobApplicationBot
│ │ ├── CosineSimilarity.java
│ │ ├── ExtractPDFText.java
│ │ ├── TFIDFCalc.java
│ │ ├── TFIDFResultContainer.java
│ │ └── TextDocument.java
│ └── test
│ ├── java
│ └── com
│ │ └── btieu
│ │ └── JobApplicationBot
│ │ ├── TFIDFCalcTest.java
│ │ ├── TestClass.java
│ │ └── WriteFilesTest.java
│ └── resources
│ └── com
│ └── btieu
│ └── JobApplicationBot
│ ├── CSVTestCases.java
│ └── TFIDFCalcTestCases.java
├── Makefile
├── README.md
├── index.html
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.jar
2 | *.class
3 | target
4 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image:
2 | name: jdsutton7/compute-node
3 |
4 | before_script:
5 | - make install
6 |
7 | install_and_build:
8 | script:
9 | - make test
10 |
11 | pages:
12 | stage: deploy
13 | script:
14 | - mkdir .public
15 | - cp -r * .public
16 | - mv .public public
17 | - make build
18 | - mv ./target/BotGUI.jar public/BotGUI.jar
19 | - make documentation
20 | - mv ./target/site public/documentation.html
21 | artifacts:
22 | paths:
23 | - public
24 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/BotGUI.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import java.awt.EventQueue;
4 |
5 | import javax.swing.JFrame;
6 | import javax.swing.JPanel;
7 |
8 | /**
9 | * Create the GUI.
10 | *
11 | * @author bruce
12 | *
13 | */
14 | public class BotGUI extends JFrame {
15 |
16 | private static final long serialVersionUID = 1L;
17 | private static final int _GUI_X_AXIS = 0;
18 | private static final int _GUI_Y_AXIS = 0;
19 | private static final int _GUI_WIDTH = 650;
20 | private static final int _GUI_HEIGHT = 650;
21 | private JPanel _contentPane;
22 | private IndeedPanel _indeedPanel;
23 | private GlassdoorPanel _glassdoorPanel;
24 | private LeverGreenhousePanel _leverGreenhousePanel;
25 | private LinkedInPanel _linkedInPanel;
26 | private static final String _GUI_TITLE = "Job Application Bot";
27 |
28 | /**
29 | * Launch the application.
30 | */
31 | public static void main(String[] args) {
32 | EventQueue.invokeLater(new Runnable() {
33 | public void run() {
34 | try {
35 | IndeedPanel indeedPanel = new IndeedPanel();
36 | GlassdoorPanel glassdoorPanel = new GlassdoorPanel();
37 | LeverGreenhousePanel leverGreenhousePanel = new LeverGreenhousePanel();
38 | LinkedInPanel linkedInPanel = new LinkedInPanel();
39 | BotGUI frame = new BotGUI(indeedPanel, glassdoorPanel, leverGreenhousePanel, linkedInPanel);
40 | frame.setVisible(true);
41 | } catch (Exception e) {
42 | e.printStackTrace();
43 | }
44 | }
45 | });
46 | }
47 |
48 | /**
49 | * Default constructor.
50 | */
51 | public BotGUI() {
52 |
53 | }
54 |
55 | /**
56 | * Create the Desktop app.
57 | *
58 | * @param indeedPanel Object which creates the indeed panel.
59 | * @param glassdoorPanel Object which creates the Glassdoor panel.
60 | * @param leverGreenhouse Object which creates the LeverGreenhouse panel.
61 | * @param linkedInPanel Object which creates the LinkedInPanel.
62 | */
63 | public BotGUI(IndeedPanel indeedPanel, GlassdoorPanel glassdoorPanel, LeverGreenhousePanel leverGreenhouse,
64 | LinkedInPanel linkedInPanel) {
65 | super(_GUI_TITLE);
66 |
67 | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
68 | setBounds(_GUI_X_AXIS, _GUI_Y_AXIS, _GUI_WIDTH, _GUI_HEIGHT);
69 | setLocationRelativeTo(null);
70 |
71 | // Create content pane to put labels and text fields on.
72 | this._contentPane = new JPanel();
73 | setContentPane(this._contentPane);
74 | this._contentPane.setLayout(null);
75 |
76 | // Create Indeed tab.
77 | this._indeedPanel = indeedPanel;
78 | this._indeedPanel.createIndeedPanel(this._contentPane);
79 | this._indeedPanel.launchApp();
80 |
81 | // Create Glassdoor tab.
82 | this._glassdoorPanel = glassdoorPanel;
83 | this._glassdoorPanel.createGlassdoorPanel(this._contentPane);
84 | this._glassdoorPanel.launchApp();
85 |
86 | // Create LeverGreenhouse tab.
87 | this._leverGreenhousePanel = leverGreenhouse;
88 | this._leverGreenhousePanel.createLeverGreenhousePanel(this._contentPane);
89 | this._leverGreenhousePanel.launchApp();
90 |
91 | // Create LinkedIn tab.
92 | this._linkedInPanel = linkedInPanel;
93 | this._linkedInPanel.createLinkedInPanel(this._contentPane);
94 | this._linkedInPanel.launchApp();
95 |
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/CharLengthDocumentListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions
6 | * are met:
7 | *
8 | * - Redistributions of source code must retain the above copyright
9 | * notice, this list of conditions and the following disclaimer.
10 | *
11 | * - Redistributions in binary form must reproduce the above copyright
12 | * notice, this list of conditions and the following disclaimer in the
13 | * documentation and/or other materials provided with the distribution.
14 | *
15 | * - Neither the name of Oracle or the names of its
16 | * contributors may be used to endorse or promote products derived
17 | * from this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20 | * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | package net.codejava.swing;
33 |
34 | import javax.swing.JTextArea;
35 | import javax.swing.event.DocumentEvent;
36 | import javax.swing.event.DocumentListener;
37 | import javax.swing.text.Document;
38 |
39 | /**
40 | * Listen to changes made to a document.
41 | *
42 | * @author Oracle
43 | *
44 | */
45 | public class CharLengthDocumentListener implements DocumentListener {
46 |
47 | private JTextArea _changeLog;
48 | private String _NEWLINE = "\n";
49 |
50 | /**
51 | * Initialize the char length log.
52 | * @param changeLog
53 | */
54 | public CharLengthDocumentListener(JTextArea changeLog) {
55 | _changeLog = changeLog;
56 | }
57 |
58 | /**
59 | * Override insertUpdate method, which is called when text is inserted into the
60 | * listened-to document.
61 | */
62 | @Override
63 | public void insertUpdate(DocumentEvent e) {
64 | displayEditInfo(e);
65 | }
66 |
67 | /**
68 | * Override the removeUpdate method, which is called when text is removed from
69 | * the listened-to document.
70 | */
71 | @Override
72 | public void removeUpdate(DocumentEvent e) {
73 | displayEditInfo(e);
74 | }
75 |
76 | /**
77 | * Override the changedUpdate method, which is called when the style of some of
78 | * the text in the listened-to document changes.
79 | */
80 | @Override
81 | public void changedUpdate(DocumentEvent e) {
82 | displayEditInfo(e);
83 | }
84 |
85 | /**
86 | * Keep track of the number of characters typed.
87 | *
88 | * @param e The event.
89 | */
90 | private void displayEditInfo(DocumentEvent e) {
91 | Document document = e.getDocument();
92 | int changeLength = e.getLength();
93 | _changeLog.append(e.getType().toString() + ": " + changeLength + " character"
94 | + ((changeLength == 1) ? ". " : "s. ") + " Text length = " + document.getLength() + "." + _NEWLINE);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/CreateGUIComponents.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions
6 | * are met:
7 | *
8 | * - Redistributions of source code must retain the above copyright
9 | * notice, this list of conditions and the following disclaimer.
10 | *
11 | * - Redistributions in binary form must reproduce the above copyright
12 | * notice, this list of conditions and the following disclaimer in the
13 | * documentation and/or other materials provided with the distribution.
14 | *
15 | * - Neither the name of Oracle or the names of its
16 | * contributors may be used to endorse or promote products derived
17 | * from this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20 | * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 |
33 | package net.codejava.swing;
34 |
35 | import com.btieu.JobApplicationBot.JobApplicationData;
36 | import com.btieu.JobApplicationBot.JobApplicationData.ApplicationType;
37 | import com.jgoodies.forms.factories.DefaultComponentFactory;
38 |
39 | import java.awt.event.ActionEvent;
40 | import java.awt.event.ActionListener;
41 | import java.io.File;
42 |
43 | import javax.swing.JButton;
44 | import javax.swing.JComboBox;
45 | import javax.swing.JFileChooser;
46 | import javax.swing.JFrame;
47 | import javax.swing.JLabel;
48 | import javax.swing.JPanel;
49 | import javax.swing.JPasswordField;
50 | import javax.swing.JScrollPane;
51 | import javax.swing.JTabbedPane;
52 | import javax.swing.JTextArea;
53 | import javax.swing.JTextField;
54 | import javax.swing.SwingConstants;
55 | import javax.swing.filechooser.FileNameExtensionFilter;
56 | import javax.swing.text.AbstractDocument;
57 |
58 | /**
59 | * This class contains the methods used to create swing panels, labels, buttons,
60 | * etc.
61 | *
62 | * @author bruce
63 | *
64 | */
65 | public class CreateGUIComponents extends JFrame {
66 |
67 |
68 | private static final long serialVersionUID = 1L;
69 | private static final int MAX_CHARACTERS = 270;
70 | private JPanel _panel;
71 | private JPanel _contentPane;
72 | private JLabel _lblNewJgoodiesTitle;
73 | private final JFileChooser _openFileChooser;
74 | private File _file;
75 | private JButton _button;
76 | private JTextField _field;
77 | private JPasswordField _password;
78 | private SingletonTab _singletonTab;
79 | private JTextArea _textArea;
80 | private JTextArea _changeLog;
81 | private FileNameExtensionFilter _filter;
82 |
83 |
84 | /**
85 | * Initialize a new JPanel and file chooser object.
86 | */
87 | public CreateGUIComponents() {
88 | _panel = new JPanel();
89 | _singletonTab = SingletonTab.getInstance();
90 | _openFileChooser = new JFileChooser();
91 | _filter = new FileNameExtensionFilter(null, "pdf");
92 | this._openFileChooser.setCurrentDirectory(new File("./"));
93 | this._openFileChooser.setFileFilter(_filter);
94 |
95 | }
96 |
97 | /**
98 | * Get the resume file path.
99 | * @return The resume file.
100 | */
101 | public File getResumeFile() {
102 | return _file;
103 | }
104 |
105 | /**
106 | * This method adds JLabels.
107 | *
108 | * @param name The name of the label to be added.
109 | * @param x The new x-coordinate of the component.
110 | * @param y The new y-coordinate of the component.
111 | * @param width The new width of the component.
112 | * @param height The new height of the component.
113 | */
114 | public JLabel addLabels(String name, int x, int y, int width, int height) {
115 | JLabel label = new JLabel(name);
116 | label.setBounds(x, y, width, height);
117 | _panel.add(label);
118 | return label;
119 | }
120 |
121 | /**
122 | * Add a fixed label which will go specifically in the LinkedInPanel.
123 | * @param x The new x-coordinate of the component.
124 | * @param y The new y-coordinate of the component.
125 | * @param width The new width of the component.
126 | * @param height The new height of the component.
127 | */
128 | public void addFixedLabel(int x, int y, int width, int height) {
129 | JLabel label = new JLabel();
130 | label.setText("
Write your message like the format below, making sure that there's always a \"Sincerely, \" at the end, "
131 | + "or an ending greeting of your choice. The beginning of the message will be substituted with \"Hi, 'person's name', \" followed by your message. ");
132 | label.setBounds(x, y, width, height);
133 | _panel.add(label);
134 | }
135 |
136 | /**
137 | * Add a text area so the user can assemble a message.
138 | * @param x The new x-coordinate of the component.
139 | * @param y The new y-coordinate of the component.
140 | * @param width The new width of the component.
141 | * @param height The new height of the component.
142 | * @return The text area as an object.
143 | */
144 | public JTextArea addTextArea(int x, int y, int width, int height) {
145 | _textArea = new JTextArea(
146 | "Example: \"your profile appeared in my search of software engineers. I am currently pursuing a career in software engineering and it would be great to hear about your journey and experience in the field. Kindly, accept my invitation. You would be a big help! Sincerely, \"",
147 | 100, 100);
148 | _textArea.setLineWrap(true);
149 | _textArea.setWrapStyleWord(true);
150 | _textArea.setBounds(x, y, width, height);
151 |
152 | AbstractDocument pDoc = (AbstractDocument) _textArea.getDocument();
153 |
154 | // Set max num of characters text area can have.
155 | pDoc.setDocumentFilter(new DocumentSizeFilter(MAX_CHARACTERS));
156 |
157 | // Create the text area for the status log and configure it.
158 | _changeLog = new JTextArea(5, 30);
159 | _changeLog.setEditable(false);
160 | JScrollPane scrollPaneForLog = new JScrollPane(_changeLog);
161 | createGoodiesTitle("Character count", 20, 440, 122, 16);
162 | scrollPaneForLog.setBounds(20, 460, 300, 50);
163 |
164 | pDoc.addDocumentListener(new CharLengthDocumentListener(_changeLog));
165 |
166 | _panel.add(scrollPaneForLog);
167 | _panel.add(_textArea);
168 | return _textArea;
169 | }
170 |
171 |
172 | /**
173 | * This method adds JTextFields.
174 | *
175 | * @param x The new x-coordinate of the component.
176 | * @param y The new y-coordinate of the component.
177 | * @param width The new width of the component.
178 | * @param height The new height of the component.
179 | * @param column The number of columns of the textfield.
180 | * @return A JTextField object.
181 | */
182 | public JTextField addTextField(int x, int y, int width, int height, int column) {
183 | _field = new JTextField();
184 | _field.setBounds(x, y, width, height);
185 | _field.setColumns(column);
186 | _panel.add(_field);
187 | return _field;
188 | }
189 |
190 | /**
191 | * This method adds a JPasswordField.
192 | *
193 | * @param x The new x-coordinate of the component.
194 | * @param y The new y-coordinate of the component.
195 | * @param width The new width of the component.
196 | * @param height The new height of the component.
197 | * @param column The number of columns of the textfield.
198 | * @return A JPasswordField object.
199 | */
200 | public JPasswordField addPasswordField(int x, int y, int width, int height, int column) {
201 | _password = new JPasswordField();
202 | _password.setBounds(x, y, width, height);
203 | _panel.add(_password);
204 | _password.setColumns(column);
205 | return _password;
206 | }
207 |
208 | /**
209 | * This method creates a tab.
210 | *
211 | * @param name The name of the tab.
212 | * @param contentPane The content object.
213 | * @param tabbedPane The tab object.
214 | * @param x The new x-coordinate of the component.
215 | * @param y The new y-coordinate of the component.
216 | * @param width The new width of the component.
217 | * @param height The new height of the component.
218 | */
219 | public void createTab(String name, JPanel contentPane, JTabbedPane tabbedPane, int x, int y, int width,
220 | int height) {
221 | tabbedPane = _singletonTab.getTabbedPane();
222 | tabbedPane.setBounds(x, y, width, height);
223 | contentPane.add(tabbedPane);
224 | tabbedPane.addTab(name, null, _panel, null);
225 | _panel.setLayout(null);
226 | }
227 |
228 | /**
229 | * This method creates titles.
230 | *
231 | * @param title The name of the title.
232 | * @param x The new x-coordinate of the component.
233 | * @param y The new y-coordinate of the component.
234 | * @param width The new width of the component.
235 | * @param height The new height of the component.
236 | */
237 | public void createGoodiesTitle(String title, int x, int y, int width, int height) {
238 | _lblNewJgoodiesTitle = DefaultComponentFactory.getInstance().createTitle(title);
239 | _lblNewJgoodiesTitle.setHorizontalAlignment(SwingConstants.CENTER);
240 | _lblNewJgoodiesTitle.setBounds(x, y, width, height);
241 | _panel.add(_lblNewJgoodiesTitle);
242 | }
243 |
244 | /**
245 | * This method creates a drop down menu of application types.
246 | *
247 | * @param applicationTypes An array of names which represents options in the dropdown.
248 | * @param x The new x-coordinate of the component.
249 | * @param y The new y-coordinate of the component.
250 | * @param width The new width of the component.
251 | * @param height The new height of the component.
252 | * @return The ApplicationType combo box.
253 | */
254 | public JComboBox addAppTypeDropdown(int x, int y, int width, int height) {
255 | JComboBox comboBox = new JComboBox(JobApplicationData.ApplicationType.values());
256 | comboBox.removeItemAt(comboBox.getItemCount() - 1); // Remove LEVER_GREENHOUSE option.
257 | comboBox.setBounds(x, y, width, height);
258 | _panel.add(comboBox);
259 | return comboBox;
260 | }
261 |
262 |
263 | /**
264 | * This method creates a drop down menu of numbers.
265 | *
266 | * @param names An array of names which represents options in the dropdown.
267 | * @param x The new x-coordinate of the component.
268 | * @param y The new y-coordinate of the component.
269 | * @param width The new width of the component.
270 | * @param height The new height of the component.
271 | * @return The Integer combo box.
272 | */
273 | public JComboBox addDropdown(Integer[] nums, int x, int y, int width, int height) {
274 | JComboBox comboBox = new JComboBox(nums);
275 | comboBox.setBounds(x, y, width, height);
276 | _panel.add(comboBox);
277 | return comboBox;
278 | }
279 |
280 |
281 | /**
282 | * This method adds a JButton.
283 | *
284 | * @param name The label displayed on the button.
285 | * @param x The new x-coordinate of the component.
286 | * @param y The new y-coordinate of the component.
287 | * @param width The new width of the component.
288 | * @param height The new height of the component.
289 | * @return A button object.
290 | */
291 | public JButton addButton(String name, int x, int y, int width, int height) {
292 | _button = new JButton(name);
293 | _button.setBounds(x, y, width, height);
294 | _panel.add(_button);
295 | return _button;
296 | }
297 |
298 | /**
299 | * This method adds functionality to upload a resume.
300 | *
301 | * @param x The new x-coordinate of the component.
302 | * @param y The new y-coordinate of the component.
303 | * @param width The new width of the component.
304 | * @param height The new height of the component.
305 | */
306 | public void addUploadResume(int x, int y, int width, int height) {
307 |
308 | JLabel chosenFilelabel = addLabels("", x, y, width, height);
309 |
310 | JButton openFileBtn = addButton("Upload resume (PDF only)", 200, 450, 200, 29);
311 | openFileBtn.addActionListener(new ActionListener() {
312 | public void actionPerformed(ActionEvent e) {
313 | int returnValue = _openFileChooser.showOpenDialog(_contentPane);
314 |
315 | if (returnValue == JFileChooser.APPROVE_OPTION) {
316 | File selectedFile = _openFileChooser.getSelectedFile();
317 | chosenFilelabel.setText("File successfully loaded!");
318 | _file = selectedFile;
319 | } else {
320 | chosenFilelabel.setText("");
321 | chosenFilelabel.setText("No file chosen!");
322 | }
323 | }
324 | });
325 |
326 | }
327 |
328 | }
329 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/DocumentSizeFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions
6 | * are met:
7 | *
8 | * - Redistributions of source code must retain the above copyright
9 | * notice, this list of conditions and the following disclaimer.
10 | *
11 | * - Redistributions in binary form must reproduce the above copyright
12 | * notice, this list of conditions and the following disclaimer in the
13 | * documentation and/or other materials provided with the distribution.
14 | *
15 | * - Neither the name of Oracle or the names of its
16 | * contributors may be used to endorse or promote products derived
17 | * from this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20 | * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | package net.codejava.swing;
33 |
34 | import javax.swing.text.*;
35 | import java.awt.Toolkit;
36 |
37 | /**
38 | * This class allows you to set a maximum character length for text.
39 | * @author Oracle
40 | *
41 | */
42 | public class DocumentSizeFilter extends DocumentFilter {
43 | int maxCharacters;
44 | boolean DEBUG = false;
45 |
46 | /**
47 | * Constructor to initialize the maximum character count.
48 | * @param maxChars
49 | */
50 | public DocumentSizeFilter(int maxChars) {
51 | maxCharacters = maxChars;
52 | }
53 |
54 | /**
55 | * Override the insertString() method in DocumentFilter.
56 | */
57 | @Override
58 | public void insertString(FilterBypass fb, int offs,
59 | String str, AttributeSet a)
60 | throws BadLocationException {
61 | if (DEBUG) {
62 | System.out.println("in DocumentSizeFilter's insertString method");
63 | }
64 |
65 | //This rejects the entire insertion if it would make
66 | //the contents too long. Another option would be
67 | //to truncate the inserted string so the contents
68 | //would be exactly maxCharacters in length.
69 | if ((fb.getDocument().getLength() + str.length()) <= maxCharacters)
70 | super.insertString(fb, offs, str, a);
71 | else
72 | Toolkit.getDefaultToolkit().beep();
73 | }
74 |
75 | /**
76 | * Override the replace() method in DocumentFilter.
77 | */
78 | @Override
79 | public void replace(FilterBypass fb, int offs,
80 | int length,
81 | String str, AttributeSet a)
82 | throws BadLocationException {
83 | if (DEBUG) {
84 | System.out.println("in DocumentSizeFilter's replace method");
85 | }
86 | //This rejects the entire replacement if it would make
87 | //the contents too long. Another option would be
88 | //to truncate the replacement string so the contents
89 | //would be exactly maxCharacters in length.
90 | if ((fb.getDocument().getLength() + str.length()
91 | - length) <= maxCharacters)
92 | super.replace(fb, offs, length, str, a);
93 | else
94 | Toolkit.getDefaultToolkit().beep();
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/GUIComponentsHelper.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | /**
4 | * Helper class for creating some components in CreateGUIComponents.
5 | * @author Bruce Tieu
6 | */
7 | public class GUIComponentsHelper {
8 |
9 | private static final int _MAX_PAGE_NUM = 50;
10 | private static final int _MAX_CONNECTION_REQUESTS = 500;
11 |
12 | /**
13 | * Generate page numbers to add as a dropdown.
14 | * @return An Integer array page numbers.
15 | */
16 | public static Integer[] generatePageNumbers(int start) {
17 | Integer pageNumContainer[] = new Integer[_MAX_PAGE_NUM];
18 | for (int i = start; i < _MAX_PAGE_NUM; i++) {
19 | pageNumContainer[i] = i;
20 | }
21 | return pageNumContainer;
22 | }
23 |
24 | /**
25 | * Generate a list of connection request numbers, the max being 500.
26 | * @return An Integer array of connection numbers.
27 | */
28 | public static Integer[] generateMaxConnectRequests() {
29 | Integer maxConnectContainer[] = new Integer[_MAX_CONNECTION_REQUESTS + 1];
30 | for (int i = 0; i <= _MAX_CONNECTION_REQUESTS; i++) {
31 | maxConnectContainer[i] = i + 1; // Start at 1.
32 | }
33 | return maxConnectContainer;
34 | }
35 |
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/GlassdoorPanel.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import java.awt.event.ActionEvent;
4 | import java.awt.event.ActionListener;
5 | import java.io.IOException;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | import javax.swing.JButton;
10 | import javax.swing.JComboBox;
11 | import javax.swing.JPanel;
12 | import javax.swing.JPasswordField;
13 | import javax.swing.JTabbedPane;
14 | import javax.swing.JTextField;
15 |
16 | import com.btieu.JobApplicationBot.JobApplicationData;
17 | import com.btieu.JobApplicationBot.JobApplicationData.ApplicationType;
18 | import com.btieu.JobApplicationBot.JobIterator;
19 | import com.btieu.JobApplicationBot.JobPostingData;
20 | import com.btieu.JobApplicationBot.Pagination;
21 | import com.btieu.JobApplicationBot.WriteFiles;
22 |
23 | /**
24 | * Create a Glassdoor tab with fields to let users apply to Glassdoor jobs.
25 | *
26 | * @author Bruce Tieu
27 | *
28 | */
29 | public class GlassdoorPanel extends CreateGUIComponents {
30 |
31 | private static final long serialVersionUID = 1L;
32 | private static final String _GLASSDOOR_URL = "https://www.glassdoor.com/index.htm";
33 | private static final int _STARTING_PAGE = 0;
34 |
35 | private JTextField _email;
36 | private JPasswordField _password;
37 | private JTextField _whatJob;
38 | private JTextField _jobLoc;
39 | private JTextField _csvOutputPath;
40 | private JComboBox _appBox;
41 | private JComboBox _pageNumBox;
42 | private JTabbedPane _tabbedPane;
43 | private List _listOfTextFields = new ArrayList<>();
44 | private Validator _validator = new Validator();
45 | private JobApplicationData _jobAppData;
46 | private WriteFiles _writeFiles;
47 | private JobIterator _jobIterator;
48 | private Pagination _page;
49 | private ApplicationType _appType;
50 |
51 | /**
52 | * Initialize a JobApplicationData.
53 | */
54 | public GlassdoorPanel() {
55 | _jobAppData = new JobApplicationData();
56 | }
57 | /**
58 | * Create the Glassdoor panel.
59 | *
60 | * @param _contentPane The panel for storing content.
61 | */
62 | public void createGlassdoorPanel(JPanel _contentPane) {
63 |
64 | createTab("Glassdoor", _contentPane, _tabbedPane, 0, 0, 650, 650);
65 | _addApplicantFields();
66 | _addJobPreferenceFields();
67 | addUploadResume(210, 475, 200, 29);
68 | }
69 |
70 | /**
71 | * This method launches the browser and grabs all information from filled out
72 | * fields.
73 | */
74 | public void launchApp() {
75 | JButton launchButton = addButton("Launch", 245, 525, 117, 29);
76 |
77 | // Disable button by default.
78 | launchButton.setEnabled(false);
79 |
80 | // Enable launch button if all TextFields are filled.
81 | _validateTextFields(launchButton);
82 |
83 | launchButton.addActionListener(new ActionListener() {
84 | public void actionPerformed(ActionEvent e) {
85 |
86 | _getCompleteFields();
87 |
88 | // Run the GlassdoorBot
89 | try {
90 | new RunGlassdoorBot(_appType, _jobAppData, _jobIterator, _page, _writeFiles);
91 | } catch (Exception e1) {
92 | MessageDialog.infoBox(MessageDialog.ERROR_RUNNING_BOT_MSG, MessageDialog.ERROR_RUNNING_BOT_TITLE);
93 | }
94 |
95 | }
96 | });
97 |
98 | }
99 |
100 | /**
101 | * Add applicant information fields.
102 | */
103 | private void _addApplicantFields() {
104 |
105 | createGoodiesTitle("Glassdoor Login Info", 230, 232, 175, 16);
106 | addLabels("Email", 150, 270, 61, 16);
107 | addLabels("Password", 150, 308, 61, 16);
108 |
109 | _email = addTextField(280, 265, 130, 26, 10);
110 | _password = addPasswordField(280, 303, 130, 26, 10);
111 |
112 | }
113 |
114 | /**
115 | * Add Job preferences fields.
116 | */
117 | private void _addJobPreferenceFields() {
118 | createGoodiesTitle("Job Preferences", 230, 32, 122, 16);
119 | addLabels("What job", 150, 65, 61, 16);
120 | addLabels("Location of job", 150, 97, 100, 16);
121 | addLabels("Application type", 150, 128, 150, 16);
122 | addLabels("Pages to scrape", 150, 156, 100, 16);
123 | addLabels("CSV output path", 150, 194, 150, 16);
124 |
125 | _whatJob = addTextField(280, 60, 130, 26, 10);
126 | _jobLoc = addTextField(280, 92, 130, 26, 10);
127 | _appBox = addAppTypeDropdown(280, 124, 150, 27);
128 | _pageNumBox = addDropdown(GUIComponentsHelper.generatePageNumbers(_STARTING_PAGE), 280, 156, 150, 27);
129 | _csvOutputPath = addTextField(280, 192, 180, 26, 10);
130 | }
131 |
132 | /**
133 | * Check if the text fields are completed by listening to each one.
134 | */
135 | private void _validateTextFields(JButton launchButton) {
136 |
137 | // Add text field to the list of text fields.
138 | _listOfTextFields.add(_email);
139 | _listOfTextFields.add(_password);
140 | _listOfTextFields.add(_whatJob);
141 | _listOfTextFields.add(_jobLoc);
142 | _listOfTextFields.add(_csvOutputPath);
143 |
144 | // Disable launch button if any text fields are blank.
145 | for (JTextField tf : _listOfTextFields) {
146 | tf.getDocument().addDocumentListener(new TextFieldListener(_listOfTextFields, launchButton));
147 | }
148 | }
149 |
150 | /**
151 | * Get the completed text field info.
152 | */
153 | private void _getCompleteFields() {
154 | _writeFiles = null;
155 |
156 | // Validate the csv output is actually a csv.
157 | try {
158 | if (_csvOutputPath.getText().endsWith(".csv") && _csvOutputPath.getText().length() > 4) {
159 | _writeFiles = new WriteFiles(_csvOutputPath.getText());
160 | }
161 | else {
162 | MessageDialog.infoBox(MessageDialog.INVALID_CSV_MSG, MessageDialog.INVALID_CSV_TITLE);
163 | return;
164 | }
165 | } catch (IOException e2) {
166 | System.out.println(e2.toString());
167 | }
168 |
169 | _jobAppData.platformUrl = _GLASSDOOR_URL;
170 | _jobAppData.email = _email.getText();
171 | _jobAppData.password = String.valueOf(_password.getPassword());
172 | _jobAppData.whatJob = _whatJob.getText();
173 | _jobAppData.locationOfJob = _jobLoc.getText();
174 |
175 | // Validate the email.
176 | if (!_validator.validateEmail(_jobAppData.email.trim())) {
177 | MessageDialog.infoBox(MessageDialog.INVALID_EMAIL_MSG, MessageDialog.INVALID_EMAIL_TITLE);
178 | return;
179 | }
180 |
181 | // Verify a resume has been uploaded.
182 | try {
183 | JobApplicationData.resumePath = getResumeFile().toString();
184 | } catch (Exception e1) {
185 | MessageDialog.infoBox(MessageDialog.NO_RESUME_MSG, MessageDialog.NO_RESUME_TITLE);
186 | return;
187 | }
188 |
189 | JobPostingData.pagesToScrape = Integer.parseInt(_pageNumBox.getSelectedItem().toString());
190 | _appType = (ApplicationType) _appBox.getSelectedItem();
191 | _jobIterator = new JobIterator(_writeFiles, _appType);
192 | _page = new Pagination(_jobAppData);
193 |
194 | }
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/IndeedPanel.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import java.awt.event.ActionEvent;
4 | import java.awt.event.ActionListener;
5 | import java.io.IOException;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | import javax.swing.JButton;
10 | import javax.swing.JComboBox;
11 | import javax.swing.JPanel;
12 | import javax.swing.JTabbedPane;
13 | import javax.swing.JTextField;
14 |
15 | import com.btieu.JobApplicationBot.JobApplicationData;
16 | import com.btieu.JobApplicationBot.JobApplicationData.ApplicationType;
17 | import com.btieu.JobApplicationBot.JobIterator;
18 | import com.btieu.JobApplicationBot.JobPostingData;
19 | import com.btieu.JobApplicationBot.Pagination;
20 | import com.btieu.JobApplicationBot.WriteFiles;
21 |
22 | /**
23 | * This class creates the Indeed panel.
24 | *
25 | * @author bruce
26 | *
27 | */
28 | public class IndeedPanel extends CreateGUIComponents {
29 |
30 | private static final long serialVersionUID = 1L;
31 | private static final int _STARTING_PAGE = 0;
32 | private static final String _INDEED_URL = "https://www.indeed.com/?from=gnav-util-homepage";
33 | private JTextField _whatJob;
34 | private JTextField _jobLoc;
35 | private JTextField _csvOutputPath;
36 | private JComboBox _appBox;
37 | private JComboBox _pageNumBox;
38 | private JTabbedPane _tabbedPane;
39 | private List _listOfTextFields = new ArrayList<>();
40 | private JobApplicationData _jobAppData;
41 | private WriteFiles _writeFiles;
42 | private JobIterator _jobIterator;
43 | private Pagination _page;
44 | private ApplicationType _appType;
45 |
46 | /**
47 | * Default constructor - initialize the JobApplicationData.
48 | */
49 | public IndeedPanel() {
50 | _jobAppData = new JobApplicationData();
51 | }
52 |
53 | /**
54 | * Create the Indeed panel with labels and application fields.
55 | *
56 | * @param _contentPane A JPanel object.
57 | */
58 | public void createIndeedPanel(JPanel _contentPane) {
59 |
60 | createTab("Indeed", _contentPane, _tabbedPane, 0, 0, 0, 0);
61 | _addJobPreferenceFields(); // Job preferences fields.
62 | addUploadResume(210, 475, 200, 29);
63 | }
64 |
65 | /**
66 | * This method launches the browser and grabs all information from filled out
67 | * fields.
68 | */
69 | public void launchApp() {
70 | JButton launchButton = addButton("Launch", 245, 525, 117, 29);
71 |
72 | // Disable button by default.
73 | launchButton.setEnabled(false);
74 |
75 | // Enable launch button if all TextFields are filled.
76 | _validateTextFields(launchButton);
77 |
78 | launchButton.addActionListener(new ActionListener() {
79 | public void actionPerformed(ActionEvent e) {
80 |
81 | _getCompleteFields();
82 |
83 | // Run the IndeedBot.
84 | try {
85 | new RunIndeedBot(_appType, _jobAppData, _jobIterator, _page);
86 | } catch (Exception e1) {
87 | MessageDialog.infoBox(MessageDialog.ERROR_RUNNING_BOT_MSG, MessageDialog.ERROR_RUNNING_BOT_TITLE);
88 | }
89 | }
90 | });
91 |
92 | }
93 |
94 | /**
95 | * Add Job preferences fields.
96 | */
97 | private void _addJobPreferenceFields() {
98 | createGoodiesTitle("Job Preferences", 230, 32, 122, 16);
99 | addLabels("What job", 150, 65, 61, 16);
100 | addLabels("Location of job", 150, 97, 100, 16);
101 | addLabels("Application type", 150, 128, 150, 16);
102 | addLabels("Pages to scrape", 150, 156, 100, 16);
103 | addLabels("CSV output path", 150, 194, 150, 16);
104 |
105 | _whatJob = addTextField(280, 60, 130, 26, 10);
106 | _jobLoc = addTextField(280, 92, 130, 26, 10);
107 | _appBox = addAppTypeDropdown(280, 124, 150, 27);
108 | _pageNumBox = addDropdown(GUIComponentsHelper.generatePageNumbers(_STARTING_PAGE), 280, 156, 150, 27);
109 | _csvOutputPath = addTextField(280, 192, 180, 26, 10);
110 | }
111 |
112 | /**
113 | * Check if the text fields are filled by listening to each one.
114 | */
115 | private void _validateTextFields(JButton launchButton) {
116 |
117 | // Add text field to the list of text fields.
118 | _listOfTextFields.add(_whatJob);
119 | _listOfTextFields.add(_jobLoc);
120 | _listOfTextFields.add(_csvOutputPath);
121 |
122 | // Disable launch button if any text fields are blank.
123 | for (JTextField tf : _listOfTextFields) {
124 | tf.getDocument().addDocumentListener(new TextFieldListener(_listOfTextFields, launchButton));
125 | }
126 | }
127 |
128 | /**
129 | * Get the completed fields.
130 | */
131 | private void _getCompleteFields() {
132 | _writeFiles = null;
133 |
134 | // Verify the csv output is actually a csv.
135 | try {
136 | if (_csvOutputPath.getText().endsWith(".csv") && _csvOutputPath.getText().length() > 4) {
137 | _writeFiles = new WriteFiles(_csvOutputPath.getText());
138 | } else {
139 | MessageDialog.infoBox(MessageDialog.INVALID_CSV_MSG, MessageDialog.INVALID_CSV_TITLE);
140 | return;
141 | }
142 |
143 | } catch (IOException e2) {
144 | System.out.println(e2.toString());
145 | }
146 |
147 | _jobAppData.platformUrl = _INDEED_URL;
148 | _jobAppData.whatJob = _whatJob.getText();
149 | _jobAppData.locationOfJob = _jobLoc.getText();
150 |
151 | // Verify a resume has been uploaded.
152 | try {
153 | JobApplicationData.resumePath = getResumeFile().toString();
154 | } catch (Exception e1) {
155 | MessageDialog.infoBox(MessageDialog.NO_RESUME_MSG, MessageDialog.NO_RESUME_TITLE);
156 | return;
157 | }
158 |
159 | JobPostingData.pagesToScrape = Integer.parseInt(_pageNumBox.getSelectedItem().toString());
160 | _appType = (ApplicationType) _appBox.getSelectedItem();
161 | _jobIterator = new JobIterator(_writeFiles, _appType);
162 | _page = new Pagination(_jobAppData);
163 | }
164 |
165 | }
166 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/LeverGreenhousePanel.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import com.btieu.JobApplicationBot.*;
4 | import com.btieu.JobApplicationBot.JobApplicationData.ApplicationType;
5 |
6 | import java.awt.event.ActionEvent;
7 | import java.awt.event.ActionListener;
8 | import java.io.IOException;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | import javax.swing.JButton;
13 | import javax.swing.JComboBox;
14 | import javax.swing.JPanel;
15 | import javax.swing.JPasswordField;
16 | import javax.swing.JTabbedPane;
17 | import javax.swing.JTextField;
18 |
19 | /**
20 | * Add a panel for Lever Greenhouse jobs to distinguish it from the Glassdoor
21 | * jobs.
22 | *
23 | * @author Bruce Tieu
24 | *
25 | */
26 | public class LeverGreenhousePanel extends CreateGUIComponents {
27 |
28 | private static final long serialVersionUID = 1L;
29 | private static final String _GLASSDOOR_URL = "https://www.glassdoor.com/index.htm";
30 |
31 | private JTextField _firstName;
32 | private JTextField _lastName;
33 | private JTextField _fullName;
34 | private JTextField _email;
35 | private JPasswordField _password;
36 | private JTextField _phoneNumber;
37 | private JTextField _school;
38 | private JTextField _location;
39 | private JTextField _company;
40 | private JTextField _linkedIn;
41 | private JTextField _github;
42 | private JTextField _portfolio;
43 | private JTextField _whatJob;
44 | private JTextField _jobLoc;
45 | private JTextField _csvOutputPath;
46 | private JComboBox _pageNumBox;
47 | private JTabbedPane _tabbedPane;
48 | private List _listOfTextFields = new ArrayList<>();
49 | private Validator _validator = new Validator();
50 | private JobApplicationData _jobAppData;
51 | private WriteFiles _writeFiles;
52 | private JobIterator _jobIterator;
53 | private Pagination _page;
54 | private ApplicationType _appType;
55 |
56 | /**
57 | * Default constructor - initialize the job application data object.
58 | */
59 | public LeverGreenhousePanel() {
60 | _jobAppData = new JobApplicationData();
61 | }
62 |
63 | /**
64 | * Create the Glassdoor panel.
65 | *
66 | * @param _contentPane The panel for storing content.
67 | */
68 | public void createLeverGreenhousePanel(JPanel _contentPane) {
69 |
70 | createTab("Lever / Greenhouse", _contentPane, _tabbedPane, 0, 0, 650, 650);
71 | _addApplicantFields();
72 | _addJobPreferenceFields();
73 | addUploadResume(210, 475, 200, 29);
74 |
75 | }
76 |
77 | /**
78 | * This method launches the browser and grabs all information from filled out
79 | * fields.
80 | */
81 | public void launchApp() {
82 | JButton launchButton = addButton("Launch", 245, 525, 117, 29);
83 |
84 | // Disable button by default.
85 | launchButton.setEnabled(false);
86 |
87 | // Enable launch button if all TextFields are filled.
88 | _validateTextFields(launchButton);
89 |
90 | launchButton.addActionListener(new ActionListener() {
91 | public void actionPerformed(ActionEvent e) {
92 |
93 | _getCompleteFields();
94 |
95 | // Run the LeverGreenhouseBot
96 | try {
97 | new RunLeverGreenhouseBot(_appType, _jobAppData, _jobIterator, _page, _writeFiles);
98 | } catch (Exception e1) {
99 | MessageDialog.infoBox(MessageDialog.ERROR_RUNNING_BOT_MSG, MessageDialog.ERROR_RUNNING_BOT_TITLE);
100 | }
101 | }
102 | });
103 |
104 | }
105 |
106 | /**
107 | * Add applicant information fields.
108 | */
109 | private void _addApplicantFields() {
110 |
111 | createGoodiesTitle("Fill below for LEVER_GREENHOUSE", 10, 33, 250, 16);
112 | addLabels("First name", 20, 65, 100, 16);
113 | addLabels("Last name", 20, 97, 100, 16);
114 | addLabels("Full name", 20, 128, 100, 16);
115 | addLabels("Phone number", 20, 161, 100, 16);
116 | addLabels("Email", 285, 270, 61, 16);
117 | addLabels("Password", 285, 308, 61, 16);
118 | addLabels("School", 20, 193, 100, 16);
119 | addLabels("Location", 20, 225, 91, 16);
120 | addLabels("Company", 20, 257, 91, 16);
121 | addLabels("LinkedIn", 20, 289, 91, 16);
122 | addLabels("GitHub", 20, 321, 91, 16);
123 | addLabels("Portfolio", 20, 353, 91, 16);
124 |
125 | createGoodiesTitle("Glassdoor Login Info", 391, 232, 175, 16);
126 |
127 | _firstName = addTextField(125, 60, 130, 26, 10);
128 | _lastName = addTextField(125, 92, 130, 26, 10);
129 | _fullName = addTextField(125, 123, 130, 26, 10);
130 | _phoneNumber = addTextField(125, 156, 130, 26, 10);
131 | _email = addTextField(401, 265, 130, 26, 10);
132 | _password = addPasswordField(401, 303, 130, 26, 10);
133 | _school = addTextField(125, 188, 130, 26, 10);
134 | _location = addTextField(125, 220, 130, 26, 10);
135 | _company = addTextField(125, 252, 130, 26, 10);
136 | _linkedIn = addTextField(125, 284, 130, 26, 10);
137 | _github = addTextField(125, 316, 130, 26, 10);
138 | _portfolio = addTextField(125, 348, 130, 26, 10);
139 |
140 | }
141 |
142 | /**
143 | * Add Job preferences fields.
144 | */
145 | private void _addJobPreferenceFields() {
146 | createGoodiesTitle("Job Preferences", 391, 32, 122, 16);
147 | addLabels("What job", 285, 65, 61, 16);
148 | addLabels("Location of job", 285, 97, 100, 16);
149 | addLabels("Pages to scrape", 285, 156, 100, 16);
150 | addLabels("CSV output path", 285, 194, 150, 16);
151 |
152 | _whatJob = addTextField(401, 60, 130, 26, 10);
153 | _jobLoc = addTextField(401, 92, 130, 26, 10);
154 | _pageNumBox = addDropdown(GUIComponentsHelper.generatePageNumbers(0), 401, 156, 150, 27);
155 | _csvOutputPath = addTextField(401, 192, 180, 26, 10);
156 | }
157 |
158 | /**
159 | * Check if the text fields are completed by listening to each one.
160 | */
161 | private void _validateTextFields(JButton launchButton) {
162 |
163 | // Add text field to the list of text fields.
164 | _listOfTextFields.add(_firstName);
165 | _listOfTextFields.add(_lastName);
166 | _listOfTextFields.add(_fullName);
167 | _listOfTextFields.add(_phoneNumber);
168 | _listOfTextFields.add(_email);
169 | _listOfTextFields.add(_password);
170 | _listOfTextFields.add(_school);
171 | _listOfTextFields.add(_location);
172 | _listOfTextFields.add(_company);
173 | _listOfTextFields.add(_whatJob);
174 | _listOfTextFields.add(_jobLoc);
175 | _listOfTextFields.add(_csvOutputPath);
176 |
177 | // Disable launch button if any text fields are blank.
178 | for (JTextField tf : _listOfTextFields) {
179 | tf.getDocument().addDocumentListener(new TextFieldListener(_listOfTextFields, launchButton));
180 | }
181 | }
182 |
183 | /**
184 | * Get the completed text field info.
185 | */
186 | private void _getCompleteFields() {
187 |
188 | // Validate the csv output is actually a csv.
189 | try {
190 | if (_csvOutputPath.getText().endsWith(".csv") && _csvOutputPath.getText().length() > 4) {
191 | _writeFiles = new WriteFiles(_csvOutputPath.getText());
192 | } else {
193 | MessageDialog.infoBox(MessageDialog.INVALID_CSV_MSG, MessageDialog.INVALID_CSV_TITLE);
194 | return;
195 | }
196 | } catch (IOException e2) {
197 | System.out.println(e2.toString());
198 | }
199 |
200 | _jobAppData.platformUrl = _GLASSDOOR_URL;
201 |
202 | _jobAppData.firstname = _firstName.getText();
203 | _jobAppData.lastname = _lastName.getText();
204 | _jobAppData.fullname = _fullName.getText();
205 | _jobAppData.phone = _phoneNumber.getText();
206 | _jobAppData.email = _email.getText();
207 | _jobAppData.password = String.valueOf(_password.getPassword());
208 | _jobAppData.school = _school.getText();
209 | _jobAppData.location = _location.getText();
210 | _jobAppData.currentCompany = _company.getText();
211 | _jobAppData.linkedin = _linkedIn.getText();
212 | _jobAppData.github = _github.getText();
213 | _jobAppData.portfolio = _portfolio.getText();
214 | _jobAppData.whatJob = _whatJob.getText();
215 | _jobAppData.locationOfJob = _jobLoc.getText();
216 |
217 | // Validate phone number.
218 | if (!_validator.validatePhone(_jobAppData.phone.trim())) {
219 | MessageDialog.infoBox(MessageDialog.INVALID_PHONE_MSG, MessageDialog.INVALID_PHONE_TITLE);
220 | return;
221 | }
222 |
223 | // Validate the email.
224 | if (!_validator.validateEmail(_jobAppData.email.trim())) {
225 | MessageDialog.infoBox(MessageDialog.INVALID_EMAIL_MSG, MessageDialog.INVALID_EMAIL_TITLE);
226 | return;
227 | }
228 |
229 | // Verify a resume has been uploaded.
230 | try {
231 | JobApplicationData.resumePath = getResumeFile().toString();
232 | } catch (Exception e1) {
233 | MessageDialog.infoBox(MessageDialog.NO_RESUME_MSG, MessageDialog.NO_RESUME_TITLE);
234 | return;
235 | }
236 |
237 | JobPostingData.pagesToScrape = Integer.parseInt(_pageNumBox.getSelectedItem().toString());
238 | _appType = JobApplicationData.ApplicationType.LEVER_GREENHOUSE;
239 | _jobIterator = new JobIterator(_writeFiles, _appType);
240 | _page = new Pagination(_jobAppData);
241 | }
242 |
243 | }
244 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/LinkedInPanel.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import com.btieu.JobApplicationBot.JobApplicationData;
4 | import com.btieu.JobApplicationBot.LinkedInPerson;
5 |
6 | import java.awt.event.ActionEvent;
7 | import java.awt.event.ActionListener;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import javax.swing.JButton;
12 | import javax.swing.JComboBox;
13 | import javax.swing.JPanel;
14 | import javax.swing.JPasswordField;
15 | import javax.swing.JTabbedPane;
16 | import javax.swing.JTextArea;
17 | import javax.swing.JTextField;
18 |
19 | /**
20 | * This class creates the LinkedIn panel.
21 | *
22 | * @author bruce
23 | *
24 | */
25 | public class LinkedInPanel extends CreateGUIComponents {
26 |
27 | private static final long serialVersionUID = 1L;
28 | private static final String _LINKEDIN_URL = "https://www.linkedin.com/login?fromSignIn=true&trk=guest_homepage-basic_nav-header-signin";
29 | private JTextField _email;
30 | private JPasswordField _password;
31 | private JTextField _firstname;
32 | private JTextField _fullname;
33 | private JTextField _linkedin;
34 | private JTextField _keywords;
35 | private JComboBox _maxConnects;
36 | private JTabbedPane _tabbedPane;
37 | private JTextArea _messageText;
38 | private List _listOfTextFields = new ArrayList<>();
39 | private Validator _validator = new Validator();
40 | private JobApplicationData _jobAppData;
41 | private LinkedInPerson _linkedInPerson;
42 |
43 | /**
44 | * Default constructor - initialize JobApplicationData and LinkedInPerson objects.
45 | */
46 | public LinkedInPanel() {
47 | _jobAppData = new JobApplicationData();
48 | _linkedInPerson = new LinkedInPerson();
49 | }
50 |
51 | /**
52 | * Create the Indeed panel with labels and application fields.
53 | *
54 | * @param _contentPane A JPanel object.
55 | */
56 | public void createLinkedInPanel(JPanel _contentPane) {
57 |
58 | createTab("LinkedIn", _contentPane, _tabbedPane, 0, 0, 650, 650);
59 | _addApplicantFields(); // Applicant info fields.
60 | _addKeywordsField();
61 | _addDropdownForMaxConnects();
62 | }
63 |
64 | /**
65 | * This method launches the browser and grabs all information from filled out
66 | * fields.
67 | */
68 | public void launchApp() {
69 | JButton launchButton = addButton("Launch", 245, 525, 117, 29);
70 |
71 | // Disable button by default.
72 | launchButton.setEnabled(false);
73 |
74 | // Enable launch button if all TextFields are filled.
75 | _validateTextFields(launchButton);
76 |
77 | launchButton.addActionListener(new ActionListener() {
78 | public void actionPerformed(ActionEvent e) {
79 |
80 | if (_isCompleteFields()) {
81 |
82 | // Run the LinkedInBot.
83 | try {
84 | new RunLinkedInBot(_jobAppData, _linkedInPerson);
85 | } catch (Exception e1) {
86 | MessageDialog.infoBox(MessageDialog.ERROR_RUNNING_BOT_MSG,
87 | MessageDialog.ERROR_RUNNING_BOT_TITLE);
88 | }
89 | }
90 |
91 | }
92 | });
93 |
94 | }
95 |
96 | /**
97 | * Add applicant information fields.
98 | */
99 | private void _addApplicantFields() {
100 | createGoodiesTitle("LinkedIn Login Info", 20, 32, 231, 16);
101 | addLabels("Email", 20, 65, 100, 16);
102 | addLabels("Password", 20, 97, 100, 16);
103 | addFixedLabel(20, 175, 500, 200);
104 | addLabels("First Name", 20, 129, 100, 16);
105 | addLabels("Full Name", 20, 161, 100, 16);
106 | addLabels("LinkedIn profile", 285, 141, 200, 16);
107 |
108 | _email = addTextField(125, 60, 130, 26, 10);
109 | _password = addPasswordField(125, 92, 130, 26, 10);
110 | _messageText = addTextArea(20, 325, 500, 100);
111 | _firstname = addTextField(125, 124, 130, 26, 10);
112 | _fullname = addTextField(125, 156, 130, 26, 10);
113 | _linkedin = addTextField(401, 136, 130, 26, 10);
114 |
115 | }
116 |
117 | /**
118 | * Add the keyword textfield.
119 | */
120 | private void _addKeywordsField() {
121 | createGoodiesTitle("Separate keywords with a comma and space", 250, 32, 300, 16);
122 | addLabels("Keywords", 285, 65, 61, 16);
123 | _keywords = addTextField(401, 60, 130, 26, 10);
124 | }
125 |
126 | /**
127 | * Add a dropdown to generate choices for max connection requests.
128 | */
129 | private void _addDropdownForMaxConnects() {
130 | addLabels("Connect requests", 285, 103, 200, 16);
131 | _maxConnects = addDropdown(GUIComponentsHelper.generateMaxConnectRequests(), 401, 98, 150, 27);
132 | }
133 |
134 | /**
135 | * Check if the text fields are completed by listening to each one.
136 | */
137 | private void _validateTextFields(JButton launchButton) {
138 |
139 | // Add text field to the list of text fields.
140 | _listOfTextFields.add(_email);
141 | _listOfTextFields.add(_password);
142 | _listOfTextFields.add(_firstname);
143 | _listOfTextFields.add(_fullname);
144 | _listOfTextFields.add(_linkedin);
145 |
146 | // Disable launch button if any text fields are blank.
147 | for (JTextField tf : _listOfTextFields) {
148 | tf.getDocument().addDocumentListener(new TextFieldListener(_listOfTextFields, launchButton));
149 | }
150 | }
151 |
152 | /**
153 | * Get the completed info from text fields.
154 | * @return True, if the fields are complete without errors, false otherwise.
155 | */
156 | private boolean _isCompleteFields() {
157 |
158 | _jobAppData.email = _email.getText();
159 | _jobAppData.password = String.valueOf(_password.getPassword());
160 | _jobAppData.firstname = _firstname.getText();
161 | _jobAppData.fullname = _fullname.getText();
162 | _jobAppData.platformUrl = _LINKEDIN_URL;
163 | _jobAppData.linkedin = _linkedin.getText();
164 |
165 | // Validate the email.
166 | if (!_validator.validateEmail(_jobAppData.email.trim())) {
167 | MessageDialog.infoBox(MessageDialog.INVALID_EMAIL_MSG, MessageDialog.INVALID_EMAIL_TITLE);
168 | return false;
169 | }
170 |
171 | _linkedInPerson.message = _messageText.getText();
172 | _linkedInPerson.keywords = _keywords.getText();
173 | LinkedInPerson.MAX_CONNECTIONS = Integer.parseInt(_maxConnects.getSelectedItem().toString());
174 |
175 | return true;
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/MessageDialog.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import javax.swing.JOptionPane;
4 |
5 | import com.btieu.JobApplicationBot.LinkedInPerson;
6 |
7 | /**
8 | * Generate a message dialog as the user interacts with the GUI.
9 | * @author bruce
10 | *
11 | */
12 | public class MessageDialog {
13 |
14 | public static final String INDEED_EASY_APPLY_MSG = "Saving Indeed Easily Apply jobs... Click Ok to continue.";
15 | public static final String INDEED_NOT_EASY_APPLY_MSG = "Saving Indeed Not Easy Apply jobs... Cick Ok to continue.";
16 | public static final String INDEED_ALL_MSG = "Saving all Indeed jobs... Click Ok to continue.";
17 |
18 | public static final String GLASSDOOR_EASY_APPLY_MSG = "Saving Glassdoor Easily Apply jobs... Click Ok to continue.";
19 | public static final String GLASSDOOR_NOT_EASY_APPLY_MSG = "Saving Glassdoor Not Easily Apply jobs... Click Ok to continue.";
20 | public static final String GLASSDOOR_ALL_MSG = "Saving all Glasdoor jobs... Click Ok to continue.";
21 |
22 | public static final String LEVER_GREENHOUSE_MSG = "Saving and Applying to Lever / Greenhouse jobs... Click Ok to continue.";
23 |
24 | public static final String LINKEDIN_MSG = "Sending connection requests... Click Ok to continue.";
25 | public static final String LINKEDIN_NO_RESULTS_MSG = "No people to connect with those keywords. Try again!";
26 |
27 | public static final String SUCCESS_JOB_SAVE_MSG = "Job successfully saved. You may search again with a different config or close the app.";
28 | public static final String SUCCESS_TITLE = "Success";
29 | public static final String SUCCESSFUL_LAUNCH_TITLE = "Successful launch";
30 | public static final String SUCCESSFUL_CONNECT_REQUEST = "Successfully sent " + LinkedInPerson.MAX_CONNECTIONS + " connect requests.";
31 |
32 | public static final String INVALID_EMAIL_MSG = "Invalid email format! Try again.";
33 | public static final String INVALID_EMAIL_TITLE = "Invalid email";
34 |
35 | public static final String NO_RESUME_MSG = "Please upload a resume! Try again.";
36 | public static final String NO_RESUME_TITLE = "No resume file supplied.";
37 |
38 | public static final String INVALID_CSV_MSG = "Path specified does not end with .csv!";
39 | public static final String INVALID_CSV_TITLE = "Invalid upload file format";
40 |
41 | public static final String INVALID_PHONE_MSG = "Incorrect phone number format! Try again.";
42 | public static final String INVALID_PHONE_TITLE = "Invalid phone number format";
43 |
44 | public static final String INVALID_KEYWORD_TITLE = "No keyword matches found.";
45 |
46 | public static final String ERROR_RUNNING_BOT_MSG = "Error running the bot. Exit app and try again.";
47 | public static final String ERROR_RUNNING_BOT_TITLE = "Fatal Error.";
48 |
49 |
50 | /**
51 | * Generate messages for the message dialog box.
52 | * @param infoMessage The message displayed inside the GUI.
53 | * @param titleBar The title of the message displayed at the top of the GUI.
54 | */
55 | public static void infoBox(String infoMessage, String titleBar) {
56 | JOptionPane.showMessageDialog(null, infoMessage, "InfoBox: " + titleBar, JOptionPane.INFORMATION_MESSAGE);
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/RunGlassdoorBot.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import java.util.List;
4 |
5 | import com.btieu.JobApplicationBot.GlassdoorApplyBot;
6 | import com.btieu.JobApplicationBot.JobApplicationData;
7 | import com.btieu.JobApplicationBot.JobIterator;
8 | import com.btieu.JobApplicationBot.Pagination;
9 | import com.btieu.JobApplicationBot.WriteFiles;
10 |
11 | import org.openqa.selenium.By;
12 | import org.openqa.selenium.WebElement;
13 |
14 | /**
15 | * Execute all methods to save and apply to a Glassdoor job.
16 | *
17 | * @author Bruce Tieu
18 | *
19 | */
20 | public class RunGlassdoorBot {
21 |
22 | public static final By _GLASSDOOR_JOB_CARD = By.className("react-job-listing");
23 |
24 | /**
25 | * When this constructor is called, it will execute the GlassdoorBot.
26 | *
27 | * @param appType The application enum type.
28 | * @param jobAppData The job application data object.
29 | * @param jobIterator The iterator object to loop through a list of jobs.
30 | * @param page The page class to go to the next page.
31 | * @param writeFiles The write files object for .csv exporting.
32 | */
33 | public RunGlassdoorBot(JobApplicationData.ApplicationType appType, JobApplicationData jobAppData,
34 | JobIterator jobIterator, Pagination page, WriteFiles writeFiles) {
35 |
36 | // Handle easy apply applications.
37 | if (appType == JobApplicationData.ApplicationType.EASILY_APPLY) {
38 | MessageDialog.infoBox(MessageDialog.GLASSDOOR_EASY_APPLY_MSG, MessageDialog.SUCCESSFUL_LAUNCH_TITLE);
39 | GlassdoorApplyBot easyApp = new GlassdoorApplyBot(jobAppData, appType);
40 | easyApp.navigateToJobPage();
41 | easyApp.login();
42 | easyApp.searchJobs();
43 | jobIterator.loopThroughJob(easyApp.tryToFindElements(_GLASSDOOR_JOB_CARD),
44 | (int index, List jobList) -> {
45 |
46 | easyApp.saveEasyApplyJobs(index, easyApp.tryToFindElements(_GLASSDOOR_JOB_CARD));
47 | }, (int pageNum) -> page.goToNextGlassdoorPage(pageNum));
48 | MessageDialog.infoBox(MessageDialog.SUCCESS_JOB_SAVE_MSG, MessageDialog.SUCCESS_TITLE);
49 |
50 | // Handle all applications.
51 | } else if (appType == JobApplicationData.ApplicationType.ALL) {
52 | MessageDialog.infoBox(MessageDialog.GLASSDOOR_ALL_MSG, MessageDialog.SUCCESSFUL_LAUNCH_TITLE);
53 | GlassdoorApplyBot greedy = new GlassdoorApplyBot(jobAppData, appType);
54 | greedy.navigateToJobPage();
55 | greedy.login();
56 | greedy.searchJobs();
57 | jobIterator.loopThroughJob(greedy.tryToFindElements(_GLASSDOOR_JOB_CARD),
58 | (int index, List jobList) -> {
59 |
60 | greedy.saveAllJobs(index, greedy.tryToFindElements(_GLASSDOOR_JOB_CARD));
61 | }, (int pageNum) -> page.goToNextGlassdoorPage(pageNum));
62 | MessageDialog.infoBox(MessageDialog.SUCCESS_JOB_SAVE_MSG, MessageDialog.SUCCESS_TITLE);
63 |
64 | // Handle not easy apply apps.
65 | } else if (appType == JobApplicationData.ApplicationType.NOT_EASY_APPLY) {
66 | MessageDialog.infoBox(MessageDialog.GLASSDOOR_EASY_APPLY_MSG, MessageDialog.SUCCESSFUL_LAUNCH_TITLE);
67 | GlassdoorApplyBot notEa = new GlassdoorApplyBot(jobAppData, appType);
68 | notEa.navigateToJobPage();
69 | notEa.login();
70 | notEa.searchJobs();
71 | jobIterator.loopThroughJob(notEa.tryToFindElements(_GLASSDOOR_JOB_CARD),
72 | (int index, List jobList) -> {
73 |
74 | notEa.saveNonEasyApplyJobs(index, notEa.tryToFindElements(_GLASSDOOR_JOB_CARD));
75 | }, (int pageNum) -> page.goToNextGlassdoorPage(pageNum));
76 | MessageDialog.infoBox(MessageDialog.SUCCESS_JOB_SAVE_MSG, MessageDialog.SUCCESS_TITLE);
77 | }
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/RunIndeedBot.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import java.util.List;
4 |
5 | import org.openqa.selenium.By;
6 | import org.openqa.selenium.WebElement;
7 |
8 | import com.btieu.JobApplicationBot.IndeedApplyBot;
9 | import com.btieu.JobApplicationBot.JobApplicationData;
10 | import com.btieu.JobApplicationBot.JobIterator;
11 | import com.btieu.JobApplicationBot.Pagination;
12 |
13 | /**
14 | * Run the IndeedBot.
15 | *
16 | * @author Bruce Tieu
17 | *
18 | */
19 | public class RunIndeedBot {
20 |
21 | // Magic string: the job div which contains all the a tags to each job link.
22 | private static final By _INDEED_JOB_CARD = By.className("jobsearch-SerpJobCard");
23 |
24 | /**
25 | * When this constructor is called, it will execute the IndeedBot.
26 | *
27 | * @param appType The application enum type.
28 | * @param jobAppData The job application data object.
29 | * @param jobIterator The iterator object to loop through a list of jobs.
30 | * @param page The page class to go to the next page.
31 | */
32 | public RunIndeedBot(JobApplicationData.ApplicationType appType, JobApplicationData jobAppData,
33 | JobIterator jobIterator, Pagination page) {
34 |
35 | // Handle easy apply applications.
36 | if (appType == JobApplicationData.ApplicationType.EASILY_APPLY) {
37 | MessageDialog.infoBox(MessageDialog.INDEED_EASY_APPLY_MSG, MessageDialog.SUCCESSFUL_LAUNCH_TITLE);
38 | IndeedApplyBot easyApp = new IndeedApplyBot(jobAppData, appType);
39 |
40 | easyApp.navigateToJobPage();
41 | easyApp.searchJobs();
42 | jobIterator.loopThroughJob(easyApp.tryToFindElements(_INDEED_JOB_CARD),
43 | (int index, List jobList) -> {
44 |
45 | easyApp.saveEasyApplyJobs(index,
46 | easyApp.tryToFindElements(_INDEED_JOB_CARD));
47 | }, (int pageNum) -> page.goToNextIndeedPage(pageNum));
48 | MessageDialog.infoBox(MessageDialog.SUCCESS_JOB_SAVE_MSG, MessageDialog.SUCCESS_TITLE);
49 |
50 | // Handle all applications.
51 | } else if (appType == JobApplicationData.ApplicationType.ALL) {
52 | MessageDialog.infoBox(MessageDialog.INDEED_ALL_MSG, MessageDialog.SUCCESSFUL_LAUNCH_TITLE);
53 | IndeedApplyBot greedy = new IndeedApplyBot(jobAppData, appType);
54 | greedy.navigateToJobPage();
55 | greedy.searchJobs();
56 | jobIterator.loopThroughJob(greedy.tryToFindElements(_INDEED_JOB_CARD),
57 | (int index, List jobList) -> {
58 |
59 | greedy.saveAllJobs(index, greedy.tryToFindElements(_INDEED_JOB_CARD));
60 | }, (int pageNum) -> page.goToNextIndeedPage(pageNum));
61 | MessageDialog.infoBox(MessageDialog.SUCCESS_JOB_SAVE_MSG, MessageDialog.SUCCESS_TITLE);
62 |
63 | // Handle not easy apply apps.
64 | } else if (appType == JobApplicationData.ApplicationType.NOT_EASY_APPLY) {
65 | MessageDialog.infoBox(MessageDialog.INDEED_NOT_EASY_APPLY_MSG, MessageDialog.SUCCESSFUL_LAUNCH_TITLE);
66 | IndeedApplyBot notEa = new IndeedApplyBot(jobAppData, appType);
67 | notEa.navigateToJobPage();
68 | notEa.searchJobs();
69 | jobIterator.loopThroughJob(notEa.tryToFindElements(_INDEED_JOB_CARD),
70 | (int index, List jobList) -> {
71 |
72 | notEa.saveNonEasyApplyJobs(index,
73 | notEa.tryToFindElements(_INDEED_JOB_CARD));
74 | }, (int pageNum) -> page.goToNextIndeedPage(pageNum));
75 | MessageDialog.infoBox(MessageDialog.SUCCESS_JOB_SAVE_MSG, MessageDialog.SUCCESS_TITLE);
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/RunLeverGreenhouseBot.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import java.util.List;
4 |
5 | import org.openqa.selenium.WebElement;
6 |
7 | import com.btieu.JobApplicationBot.JobApplicationData;
8 | import com.btieu.JobApplicationBot.JobIterator;
9 | import com.btieu.JobApplicationBot.LeverGreenhouseBot;
10 | import com.btieu.JobApplicationBot.Pagination;
11 | import com.btieu.JobApplicationBot.WriteFiles;
12 |
13 | /**
14 | * Execute all methods to apply to Lever and Greenhouse jobs.
15 | *
16 | * @author Bruce Tieu
17 | *
18 | */
19 | public class RunLeverGreenhouseBot {
20 |
21 | public RunLeverGreenhouseBot(JobApplicationData.ApplicationType appType, JobApplicationData jobAppData,
22 | JobIterator jobIterator, Pagination page, WriteFiles writeFiles) {
23 | if (appType == JobApplicationData.ApplicationType.LEVER_GREENHOUSE) {
24 | MessageDialog.infoBox(MessageDialog.LEVER_GREENHOUSE_MSG, MessageDialog.SUCCESSFUL_LAUNCH_TITLE);
25 | LeverGreenhouseBot lg = new LeverGreenhouseBot(jobAppData, appType, writeFiles);
26 | lg.navigateToJobPage();
27 | lg.login();
28 | lg.searchJobs();
29 | jobIterator.loopThroughJob(lg.tryToFindElements(RunGlassdoorBot._GLASSDOOR_JOB_CARD),
30 | (int index, List jobList) -> {
31 |
32 | lg.saveLGJobs(index, lg.tryToFindElements(RunGlassdoorBot._GLASSDOOR_JOB_CARD));
33 | }, (int pageNum) -> page.goToNextGlassdoorPage(pageNum));
34 |
35 | // Apply to lever and greenhouse jobs.
36 | lg.apply();
37 | MessageDialog.infoBox(MessageDialog.SUCCESS_JOB_SAVE_MSG, MessageDialog.SUCCESS_TITLE);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/RunLinkedInBot.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import com.btieu.JobApplicationBot.JobApplicationData;
4 | import com.btieu.JobApplicationBot.LinkedInBot;
5 | import com.btieu.JobApplicationBot.LinkedInPerson;
6 |
7 | /**
8 | * Class to execute the LinkedInBot.
9 | *
10 | * @author bruce
11 | *
12 | */
13 | public class RunLinkedInBot {
14 | /**
15 | * This constructor will execute the LinkedInBot when instantiated.
16 | *
17 | * @param jobAppData The JobApplicationData object will all applicant data.
18 | * @param linkedInPerson The LinkedInPerson object represent any given profile.
19 | */
20 | public RunLinkedInBot(JobApplicationData jobAppData, LinkedInPerson linkedInPerson) {
21 | MessageDialog.infoBox(MessageDialog.LINKEDIN_MSG, MessageDialog.SUCCESSFUL_LAUNCH_TITLE);
22 | LinkedInBot linkedinBot = new LinkedInBot(jobAppData, linkedInPerson);
23 | linkedinBot.navigateToJobPage();
24 | linkedinBot.login();
25 | linkedinBot.goToProfile();
26 | linkedinBot.aggregatePeopleProfiles();
27 | linkedinBot.connect();
28 |
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/SingletonTab.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import javax.swing.JTabbedPane;
4 |
5 | /**
6 | * Singleton class - Only create one tab in the GUI, not multiple.
7 | *
8 | * @author Bruce Tieu
9 | *
10 | */
11 | public class SingletonTab {
12 |
13 | private static SingletonTab _singleInstance = null;
14 | private JTabbedPane _tabbedPane;
15 |
16 | /**
17 | * Instantiate the JTabbedPane object.
18 | */
19 | private SingletonTab() {
20 | _tabbedPane = new JTabbedPane(JTabbedPane.TOP);
21 | }
22 |
23 | /**
24 | * Ensure only one instance of this class is created.
25 | *
26 | * @return The single instance.
27 | */
28 | public static SingletonTab getInstance() {
29 | if (_singleInstance == null) {
30 | _singleInstance = new SingletonTab();
31 | }
32 | return _singleInstance;
33 | }
34 |
35 | /**
36 | * Get the tabbed pane.
37 | *
38 | * @return The JTabbedPane object.
39 | */
40 | public JTabbedPane getTabbedPane() {
41 | return _tabbedPane;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/TextFieldListener.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import java.util.List;
4 |
5 | import javax.swing.JButton;
6 | import javax.swing.JTextField;
7 | import javax.swing.event.DocumentEvent;
8 | import javax.swing.event.DocumentListener;
9 |
10 | /**
11 | * Listen for changes in the text field and respond accordingly. Make some fields a requirement.
12 | *
13 | * @author Bruce Tieu
14 | *
15 | */
16 | public class TextFieldListener implements DocumentListener {
17 |
18 | private List _listOfTextfields;
19 | private JButton _launchButton;
20 |
21 | /**
22 | * Initialize the textfield and launch button which are 'listened' to.
23 | *
24 | * @param textfield The textfield object.
25 | * @param launchButton The button which launches the app.
26 | */
27 | public TextFieldListener(List listOfTextfields, JButton launchButton) {
28 | _listOfTextfields = listOfTextfields;
29 | _launchButton = launchButton;
30 | }
31 |
32 | @Override
33 | public void insertUpdate(DocumentEvent e) {
34 | changedUpdate(e);
35 |
36 | }
37 |
38 | @Override
39 | public void removeUpdate(DocumentEvent e) {
40 | changedUpdate(e);
41 |
42 | }
43 |
44 | @Override
45 | public void changedUpdate(DocumentEvent e) {
46 | boolean isEnabled = true;
47 |
48 | // Enable the launch button if and only if all textfields are filled out.
49 | for (JTextField tf : _listOfTextfields) {
50 | if (tf.getText().isEmpty()) {
51 | isEnabled = false;
52 | }
53 | }
54 |
55 | _launchButton.setEnabled(isEnabled);
56 |
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/app/net/codejava/swing/Validator.java:
--------------------------------------------------------------------------------
1 | package net.codejava.swing;
2 |
3 | import java.util.regex.Matcher;
4 | import java.util.regex.Pattern;
5 |
6 | /**
7 | * Class to make sure the email and phone number are of the correct format.
8 | *
9 | * @author Bruce Tieu
10 | *
11 | */
12 | public class Validator {
13 | private Pattern _emailPattern;
14 | private Pattern _phonePattern;
15 | private Matcher _emailMatcher;
16 | private Matcher _phoneMatcher;
17 |
18 | // Common email regex.
19 | private static final String _EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
20 | + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
21 |
22 | // USA phone regex pattern.
23 | private static final String _PHONE_PATTERN = "^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$";
24 |
25 | /**
26 | * Compile the email regular expression and phone regex.
27 | */
28 | public Validator() {
29 | _emailPattern = Pattern.compile(_EMAIL_PATTERN);
30 | _phonePattern = Pattern.compile(_PHONE_PATTERN);
31 | }
32 |
33 | /**
34 | * Validate email with the regex.
35 | *
36 | * @param email The email passed in.
37 | * @return True, if the email matches the regex, false otherwise.
38 | */
39 | public boolean validateEmail(final String email) {
40 | _emailMatcher = _emailPattern.matcher(email);
41 | return _emailMatcher.matches();
42 |
43 | }
44 |
45 | /**
46 | * Validate the phone with regex.
47 | *
48 | * @param phone The phone number passed in.
49 | * @return True, if the phone number matches the regex, false otherwise.
50 | */
51 | public boolean validatePhone(final String phone) {
52 | _phoneMatcher = _phonePattern.matcher(phone);
53 | return _phoneMatcher.matches();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/ApplyInterface.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.util.List;
4 |
5 | import org.openqa.selenium.WebElement;
6 |
7 | @FunctionalInterface
8 | public interface ApplyInterface {
9 | /**
10 | * Abstract method that is implemented by lamda expression.
11 | *
12 | * @param index Index of the job.
13 | * @param jobList The job list.
14 | */
15 | public void handleJob(int index, List jobList);
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/Bot.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.net.HttpURLConnection;
6 | import java.net.URL;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.concurrent.TimeUnit;
10 |
11 | import org.openqa.selenium.By;
12 | import org.openqa.selenium.interactions.Actions;
13 | import org.openqa.selenium.JavascriptExecutor;
14 | import org.openqa.selenium.NoSuchElementException;
15 | import org.openqa.selenium.support.ui.ExpectedConditions;
16 | import org.openqa.selenium.support.ui.Select;
17 | import org.openqa.selenium.support.ui.WebDriverWait;
18 | import org.openqa.selenium.WebDriver;
19 | import org.openqa.selenium.WebElement;
20 |
21 | /**
22 | * This class holds common bot actions like waiting and navigating.
23 | *
24 | * @author Bruce Tieu
25 | */
26 | public class Bot {
27 |
28 | private SingletonDriver _driver;
29 |
30 | /**
31 | * This the default constructor which only initializes the Singleton class.
32 | */
33 | public Bot() {
34 | // Every time we want to use a method from this class, we only want to open the same instance
35 | // (don't want to open multiple browsers).
36 | _driver = SingletonDriver.getInstance();
37 | }
38 |
39 | /**
40 | * This is a getter method.
41 | *
42 | * @return The driver object.
43 | */
44 | public WebDriver getWebDriver() {
45 | return _driver.getWebDriver();
46 | }
47 |
48 | /**
49 | * This is a getter method.
50 | *
51 | * @return The wait object.
52 | */
53 | public WebDriverWait getWait() {
54 | return _driver.getWait();
55 | }
56 |
57 | /**
58 | * This is a getter method.
59 | *
60 | * @return The actions object.
61 | */
62 | public Actions getActions() {
63 | return _driver.getActions();
64 | }
65 |
66 | /**
67 | * Quit the browser.
68 | */
69 | public void quitBrowser() {
70 | _driver.getWebDriver().quit();
71 | }
72 |
73 | /**
74 | * This method tries to find a single element on a page.
75 | *
76 | * @param by The specific element to be located.
77 | * @return The element if found.
78 | */
79 | public WebElement tryToFindElement(By by) {
80 | WebElement element = null;
81 | try {
82 | element = getWait().until(ExpectedConditions.visibilityOfElementLocated(by));
83 | } catch (Exception e) {
84 | System.out.println("Could not find element: " + by);
85 | }
86 | return element;
87 | }
88 |
89 | /**
90 | * This method tries to find a list of elements on a page.
91 | *
92 | * @param by The elements to be located
93 | * @return The list of elements if found.
94 | */
95 | public List tryToFindElements(By by) {
96 | List element = null;
97 | try {
98 | element = getWait().until(ExpectedConditions.visibilityOfAllElementsLocatedBy(by));
99 | } catch (Exception e) {
100 | System.out.println("Could not find element: " + by);
101 | }
102 | return element;
103 | }
104 |
105 | /**
106 | * Try to select an option from a dropdown.
107 | *
108 | * @param by The element to be located.
109 | * @param selection The text to be selected.
110 | * @return The selected option, if found.
111 | */
112 | public Select tryToSelectFromDpn(By by, String selection) {
113 | Select dropdown = null;
114 | try {
115 | dropdown = new Select(tryToFindElement(by));
116 | dropdown.selectByVisibleText(selection);
117 | } catch (Exception e) {
118 | System.out.println("Could not find element to select: " + by);
119 | }
120 | return dropdown;
121 | }
122 |
123 | /**
124 | * Try looking for an element and send text to it.
125 | *
126 | * @param by The element to be found.
127 | * @param key The text to be sent.
128 | * @return The element, if found.
129 | */
130 | public WebElement tryToFindElementAndSendKeys(By by, String key) {
131 | WebElement element = null;
132 | try {
133 | element = _driver.getWait().until(ExpectedConditions.visibilityOf(_driver.getWebDriver().findElement(by)));
134 | typeLikeAHuman(element, key);
135 | } catch (Exception e) {
136 | System.out.println("Could not send keys to: " + by);
137 | }
138 | return element;
139 | }
140 |
141 | public WebElement tryToFindElementAndSendKeysFast(By by, String key) {
142 | WebElement element = null;
143 | try {
144 | element = _driver.getWait().until(ExpectedConditions.visibilityOf(_driver.getWebDriver().findElement(by)));
145 | element.sendKeys(key);
146 | } catch (Exception e) {
147 | System.out.println("Could not send keys to: " + by);
148 | }
149 | return element;
150 | }
151 |
152 | /**
153 | * This method waits for a specific element to appear before clicking.
154 | *
155 | * @param by The specific element.
156 | */
157 | public void waitOnElementAndClick(By by) {
158 | try {
159 | getWait().until(ExpectedConditions.visibilityOf(getWebDriver().findElement(by))).click();
160 | } catch (Exception e) {
161 | System.out.println("Could not locate element to click: " + by);
162 | }
163 | }
164 |
165 | /**
166 | * This method switches tabs while applying.
167 | *
168 | * @param driver The webdriver object.
169 | * @param link The job link which is a string.
170 | */
171 | public void navigateToLinkInNewTab(String link) {
172 | // Use JavaScript to open a new tab instead of "control + t".
173 | ((JavascriptExecutor) _driver.getWebDriver()).executeScript("window.open()");
174 | // Store the available windows in a list.
175 | ArrayList tabs = new ArrayList(getWebDriver().getWindowHandles());
176 | // Switch to the newly opened tab.
177 | getWebDriver().switchTo().window(tabs.get(tabs.size() - 1));
178 | // Navigate to the job link in that newly opened tab.
179 | getWebDriver().get(link);
180 | }
181 |
182 | /**
183 | * This method switches iframes.
184 | *
185 | * @param by This is the specific iframe element to switch to.
186 | */
187 | public void switchIframes(By by) {
188 | getWait().until(ExpectedConditions.visibilityOfElementLocated(by));
189 | getWebDriver().switchTo().frame(getWebDriver().findElement(by));
190 |
191 | }
192 |
193 | /**
194 | * This is the method which mimics human typing.
195 | *
196 | * @param element The field which is typed into.
197 | * @param jobData The job application data that is supplied
198 | * @throws InterruptedException
199 | */
200 | public void typeLikeAHuman(WebElement element, String jobData) throws InterruptedException {
201 | if (element != null) {
202 | for (int i = 0; i < jobData.length(); i++) {
203 | TimeUnit.SECONDS.sleep((long) (Math.random() * (3 - 1) * +1));
204 | char c = jobData.charAt(i);
205 | String s = new StringBuilder().append(c).toString();
206 | element.sendKeys(s);
207 | }
208 | }
209 |
210 | }
211 |
212 | /**
213 | * Match the job description text to the resume.
214 | *
215 | * @return The cosine similarity number.
216 | * @throws IOException Catch any file errors.
217 | */
218 | public double jobMatchScore(By by) throws IOException {
219 |
220 | String jobDescriptionString = tryToFindElement(by).getText();
221 | TextDocument jobDescriptionText = new TextDocument(jobDescriptionString);
222 |
223 | // JobApplicationData.resumePath is the resume uploaded by the user.
224 | TextDocument resumeText = new TextDocument(new File(JobApplicationData.resumePath));
225 | return CosineSimilarity.cosineSimilarity(jobDescriptionText, resumeText);
226 | }
227 |
228 | /**
229 | * Assemble a request and get the url of it.
230 | *
231 | * @param href The url.
232 | * @return The request url.
233 | */
234 | public String getRequestURL(String href) {
235 | HttpURLConnection connection = null;
236 | try {
237 | URL url = new URL(href);
238 | connection = (HttpURLConnection) url.openConnection();
239 | connection.getContent();
240 | } catch (IOException e) {
241 | e.getMessage();
242 | }
243 | return connection.getURL().toString();
244 | }
245 |
246 | /**
247 | * Get the text of a parent node but not the text of any of the child nodes.
248 | *
249 | * @param element The parent element.
250 | * @return The text.
251 | */
252 | public String getTextExcludingChildren(WebElement element) {
253 | return (String) ((JavascriptExecutor) _driver.getWebDriver()).executeScript(
254 | "let parent = arguments[0];"
255 | + "let child = parent.firstChild;"
256 | + "let text = '';"
257 | + "while(child) {"
258 | + " if (child.nodeType === Node.TEXT_NODE) {"
259 | + " text += child.textContent;" + " }"
260 | + " child = child.nextSibling;"
261 | + "}"
262 | + "return text;", element);
263 | }
264 |
265 | /**
266 | * Check if an element exists.
267 | * @param by The element.
268 | * @return True, if it exists and false otherwise.
269 | */
270 | public boolean elementExists(By by) {
271 | try {
272 | getWebDriver().findElement(by);
273 | } catch (NoSuchElementException e) {
274 | return false;
275 | }
276 | return true;
277 | }
278 |
279 | /**
280 | * Check if an element has been clicked.
281 | * @param by The element to be clicked.
282 | * @return True, if able to be clicked and false otherwise.
283 | */
284 | public boolean isClicked(By by) {
285 | try {
286 | waitOnElementAndClick(by);
287 | return true;
288 | } catch (Exception e) {
289 | return false;
290 | }
291 |
292 | }
293 |
294 | }
295 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/GlassdoorApplyBot.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 |
6 | import org.openqa.selenium.By;
7 | import org.openqa.selenium.WebElement;
8 |
9 | import com.btieu.JobApplicationBot.JobApplicationData.ApplicationType;
10 |
11 | /**
12 | * A GlassdoorApplyBot is a GlassdoorBot. Define methods which will implement the
13 | * interface with lambda expressions.
14 | *
15 | * @author Bruce Tieu
16 | *
17 | */
18 | public class GlassdoorApplyBot extends GlassdoorBot {
19 |
20 | private JobApplicationData _jobAppData;
21 | private JobApplicationData.ApplicationType _appType;
22 |
23 | /**
24 | * Parameterized constructor to initialize JobApplicationData.
25 | *
26 | * @param jobAppData The JobApplicationData object.
27 | * @param appType The enum holding application types.
28 | */
29 | public GlassdoorApplyBot(JobApplicationData jobAppData, ApplicationType appType) {
30 | super(jobAppData, appType);
31 | _jobAppData = jobAppData;
32 | _appType = appType;
33 | }
34 |
35 | /**
36 | * Find all the Glassdoor easy apply jobs on a given page.
37 | *
38 | * @param index The particular index in the list of jobs.
39 | * @param jobList The list of all jobs.
40 | * @throws IOException
41 | * @throws InterruptedException
42 | */
43 | public void saveEasyApplyJobs(int index, List jobList) {
44 |
45 | boolean isEasyApply = jobList.get(index).findElements(By.className("jobLabel")).size() > 0;
46 |
47 | if (isEasyApply) {
48 | String jobLink = getJobViewLink(index, jobList);
49 | saveJob(jobLink, _appType);
50 | System.out.println(jobLink);
51 | }
52 | }
53 |
54 | /**
55 | * Find all glassdoor non easy jobs and save them.
56 | *
57 | * @param index The particular index in the list of jobs.
58 | * @param jobList The list of jobs.
59 | */
60 | public void saveNonEasyApplyJobs(int index, List jobList) {
61 | boolean isEasyApply = (jobList.get(index).findElements(By.className("jobLabel")).size() > 0);
62 |
63 | if (!isEasyApply) {
64 | String jobLink = getJobViewLink(index, jobList);
65 | jobLink = jobLink.replaceAll("GD_JOB_AD", "GD_JOB_VIEW");
66 | System.out.println(getRequestURL(jobLink));
67 | saveJob(getRequestURL(jobLink), _appType);
68 | }
69 |
70 | }
71 |
72 | /**
73 | * Find both easy apply and not easy apply jobs.
74 | *
75 | * @param index The particular index in the list of jobs.
76 | * @param jobList The list of all jobs.
77 | */
78 | public void saveAllJobs(int index, List jobList) {
79 |
80 | String jobLink = getJobViewLink(index, jobList);
81 | jobLink = jobLink.replaceAll("GD_JOB_AD", "GD_JOB_VIEW");
82 | saveJob(getRequestURL(jobLink), _appType);
83 | System.out.println(jobLink);
84 |
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/GlassdoorBot.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.IOException;
4 | import java.text.SimpleDateFormat;
5 | import java.util.Date;
6 | import java.util.List;
7 |
8 | import org.openqa.selenium.By;
9 | import org.openqa.selenium.Keys;
10 | import org.openqa.selenium.WebElement;
11 |
12 | /**
13 | * Apply for jobs on Glassdoor.com.
14 | *
15 | * @author bruce
16 | *
17 | */
18 | public class GlassdoorBot extends Bot {
19 | private JobApplicationData _jobAppData;
20 | private JobApplicationData.ApplicationType _appType;
21 | private String _parentWindow;
22 |
23 | public GlassdoorBot(JobApplicationData _jobAppData, JobApplicationData.ApplicationType _appType) {
24 | this._jobAppData = _jobAppData;
25 | this._appType = _appType;
26 | }
27 |
28 | /**
29 | * Navigate to the Indeed site.
30 | */
31 | public void navigateToJobPage() {
32 | getWebDriver().get(this._jobAppData.platformUrl);
33 | }
34 |
35 | /**
36 | * This method logs in to the job site.
37 | *
38 | * @throws InterruptedException If the thread executing the method is
39 | * interrupted, stop the method and return early.
40 | */
41 | public void login() {
42 |
43 | // Wait for element to appear before clicking on it.
44 | waitOnElementAndClick(By.className("locked-home-sign-in"));
45 |
46 | WebElement userEmail = tryToFindElement(By.id("userEmail"));
47 | WebElement userPassword = tryToFindElement(By.id("userPassword"));
48 |
49 | userEmail.clear();
50 | userPassword.clear();
51 |
52 | userEmail.sendKeys(this._jobAppData.email);
53 | userPassword.sendKeys(this._jobAppData.password);
54 |
55 | userPassword.submit();
56 |
57 | }
58 |
59 | /**
60 | * This method searches for jobs based on job position name and location.
61 | *
62 | * @throws InterruptedException If the thread executing the method is
63 | * interrupted, stop the method and return early.
64 | */
65 | public void searchJobs() {
66 |
67 | // First clear any populated search fields.
68 | tryToFindElement(By.id("sc.keyword")).clear();
69 | tryToFindElement(By.id("sc.location")).clear();
70 |
71 | tryToFindElementAndSendKeys(By.id("sc.keyword"), this._jobAppData.whatJob);
72 |
73 | // Send in the location of the job and search.
74 | getActions().sendKeys(Keys.TAB);
75 | getActions().sendKeys(Keys.TAB);
76 | getActions().sendKeys(Keys.DELETE);
77 | getActions().build().perform();
78 |
79 | tryToFindElementAndSendKeys(By.id("sc.location"), this._jobAppData.locationOfJob).submit();;
80 |
81 | }
82 |
83 | /**
84 | * Get the page to view the job description.
85 | *
86 | * @param index The index of the current job in the list of job cards.
87 | * @return The link of the job.
88 | * @throws IOException
89 | */
90 | public String getJobViewLink(int index, List jobList) {
91 |
92 | _parentWindow = getWebDriver().getWindowHandle(); // Get the current window.
93 | WebElement div = jobList.get(index).findElement(By.className("d-flex"));
94 | String href = div.findElement(By.className("jobLink")).getAttribute("href");
95 | navigateToLinkInNewTab(href); // Open that job in a new tab.
96 | return href;
97 | }
98 |
99 | /**
100 | * Save each job to a container.
101 | *
102 | * @param jobLink The application page.
103 | * @param appType The type of application it was.
104 | * @throws InterruptedException Catch any elements that are not found.
105 | * @throws IOException Catch and file writing errors.
106 | */
107 | public void saveJob(String jobLink, JobApplicationData.ApplicationType appType) {
108 |
109 | try {
110 | boolean hasJobInContainer = JobPostingData.jobPostingContainer
111 | .contains(getJobInformation(jobLink, appType, false));
112 |
113 | if (!hasJobInContainer) {
114 | JobPostingData.jobPostingContainer.add(getJobInformation(jobLink, appType, false)); // Save job.
115 | getWebDriver().close(); // Close that new window (the job that was opened).
116 | getWebDriver().switchTo().window(_parentWindow); // Switch back to the parent window (job listing
117 | // window).
118 | }
119 | } catch (IOException e) {
120 | System.out.println(e.getMessage());
121 | }
122 | }
123 |
124 | /**
125 | * Overloaded savejob function to accept an additional parameter.
126 | *
127 | * @param appType The application type.
128 | * @param jobList The job list.
129 | * @param index The specfic index in the job list.
130 | */
131 | public void saveJob(JobApplicationData.ApplicationType appType, List jobList, int index) {
132 | JobPostingData.jobPostingContainer.add(getJobInfoOnCard(jobList, index, appType, false));
133 | }
134 |
135 |
136 | /**
137 | * Scrape the job view page for the company name, job title, location, if it was
138 | * applied to, and the job status.
139 | */
140 | public JobPostingData getJobInformation(String jobLink, JobApplicationData.ApplicationType appType, boolean applied)
141 | throws IOException {
142 |
143 | String remote, submitted;
144 |
145 | Date date = new Date();
146 | SimpleDateFormat formatter = new SimpleDateFormat("MM-dd-yyyy HH:mm");
147 |
148 | WebElement jobContainer = tryToFindElement(By.id("JobView"));
149 | WebElement empInfo = jobContainer.findElement(By.className("smarterBannerEmpInfo"));
150 | WebElement flexColumn = empInfo.findElement(By.className("flex-column"));
151 | WebElement div = flexColumn.findElement(By.tagName("div"));
152 | List divs = div.findElements(By.tagName("div"));
153 | WebElement companyName = divs.get(0);
154 |
155 | String companyNameString = getTextExcludingChildren(companyName);
156 | String jobTitleString = divs.get(1).getText();
157 | String jobLocationString = divs.get(2).getText();
158 |
159 | if (jobLocationString.toLowerCase().contains("remote"))
160 | remote = "yes";
161 | else
162 | remote = "no";
163 |
164 | if (applied)
165 | submitted = "yes";
166 | else
167 | submitted = "no";
168 |
169 | // Return a new JobPostingData object.
170 | return new JobPostingData(jobMatchScore(By.id("JobDescriptionContainer")), jobTitleString, companyNameString,
171 | jobLocationString, remote, formatter.format(date), appType.name(), jobLink, submitted, "");
172 | }
173 |
174 | /**
175 | * Get the info on the job card, skip job matching.
176 | *
177 | * @param jobList The job list.
178 | * @param index The index in the list.
179 | * @param appType The application type.
180 | * @param applied If the job was applied to or not.
181 | * @return A JobPostingContainer object.
182 | */
183 | public JobPostingData getJobInfoOnCard(List jobList, int index,
184 | JobApplicationData.ApplicationType appType, boolean applied) {
185 |
186 | String remote, submitted;
187 |
188 | Date date = new Date();
189 | SimpleDateFormat formatter = new SimpleDateFormat("MM-dd-yyyy HH:mm");
190 |
191 | String companyNameString = jobList.get(index).findElement(By.className("pl-sm"))
192 | .findElement(By.className("jobLink")).getText();
193 | String jobTitleString = jobList.get(index).findElement(By.className("pl-sm"))
194 | .findElement(By.className("jobInfoItem")).getText();
195 | String jobLocationString = jobList.get(index).findElement(By.className("pl-sm"))
196 | .findElement(By.className("loc")).getText();
197 | String jobLink = jobList.get(index).findElement(By.className("pl-sm")).findElement(By.className("jobLink"))
198 | .getAttribute("href");
199 | jobLink = jobLink.replaceAll("GD_JOB_AD", "GD_JOB_VIEW");
200 |
201 | if (jobLocationString.toLowerCase().contains("remote"))
202 | remote = "yes";
203 | else
204 | remote = "no";
205 |
206 | if (applied)
207 | submitted = "yes";
208 | else
209 | submitted = "no";
210 |
211 | System.out.println(getRequestURL(jobLink));
212 | return new JobPostingData(0, jobTitleString, companyNameString, jobLocationString, remote, formatter.format(date),
213 | appType.name(), getRequestURL(jobLink), submitted, "");
214 |
215 | }
216 |
217 |
218 | }
219 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/GreenhouseForms.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.List;
6 |
7 | import org.openqa.selenium.By;
8 | import org.openqa.selenium.WebElement;
9 |
10 |
11 | /**
12 | * Fill out as many fields of Greenhouse forms as possible.
13 | *
14 | * @author bruce
15 | *
16 | */
17 | public class GreenhouseForms {
18 |
19 | private Bot _botAction;
20 | private JobApplicationData _jobAppData;
21 | private JobApplicationData.ApplicationType _appType;
22 |
23 | public GreenhouseForms() {
24 | _botAction = new Bot(); // Use the methods in Bot.
25 | }
26 |
27 | /**
28 | * Fill basic applicant info such as first name, last name, email, phone, etc.
29 | *
30 | * @param jobAppData
31 | */
32 | public void fillAllBasicInfo(JobApplicationData jobAppData) {
33 | _botAction.tryToFindElementAndSendKeys(By.id("first_name"), jobAppData.firstname);
34 | _botAction.tryToFindElementAndSendKeys(By.id("last_name"), jobAppData.lastname);
35 | _botAction.tryToFindElementAndSendKeys(By.id("email"), jobAppData.email);
36 | _botAction.tryToFindElementAndSendKeys(By.id("phone"), jobAppData.phone);
37 | _botAction.tryToFindElementAndSendKeys(By.id("job_application_answers_attributes_0_text_value"),
38 | jobAppData.linkedin);
39 |
40 | _botAction.tryToFindElementAndSendKeys(By.id("job_application_location"), jobAppData.location); // city
41 | List cities = _botAction.tryToFindElements(By.className("ui-menu-item"));
42 |
43 | try {
44 | for (WebElement city : cities) {
45 | if (city.getText().contains(jobAppData.location)) {
46 | city.click();
47 | }
48 | }
49 | } catch (Exception e) {
50 | System.out.println("Could not click on City");
51 | }
52 |
53 | }
54 |
55 |
56 | /**
57 | * Handle all cases of work authentication and visa questions.
58 | *
59 | * @param jobAppData The job application object.
60 | */
61 | public void fillAllWorkAuth() {
62 |
63 | _botAction.tryToSelectFromDpn(By.id("job_application_answers_attributes_1_boolean_value"), "Yes"); // work auth
64 | _botAction.tryToSelectFromDpn(By.id("job_application_answers_attributes_2_boolean_value"), "No"); // Visa
65 | _botAction.tryToSelectFromDpn(By.id("job_application_answers_attributes_3_boolean_value"), "No");
66 | _botAction.tryToSelectFromDpn(
67 | By.id("job_application_answers_attributes_3_answer_selected_options_attributes_3_question_option_id"),
68 | "Acknowledge/Confirm"); // Acknowledge/Confirm
69 | _botAction.tryToSelectFromDpn(By.id("job_application_answers_attributes_4_boolean_value"), "Yes"); // custom
70 | _botAction.tryToFindElementAndSendKeys(By.xpath("//input[contains(@autocomplete, 'visa')]"), "No"); // Visa
71 | }
72 |
73 | /**
74 | * Fill out questions regarding where you found the application. In this case,
75 | * it's glassdoor.
76 | */
77 | public void fillAllHowDidYouFindUs() {
78 | _botAction.tryToSelectFromDpn(
79 | By.id("job_application_answers_attributes_2_answer_selected_options_attributes_2_question_option_id"),
80 | "Glassdoor"); // How did you hear about us?
81 | _botAction.tryToFindElementAndSendKeys(By.id("job_application_answers_attributes_2_text_value"), "Glassdoor");
82 | _botAction.tryToSelectFromDpn(By.tagName("select"), "Glassdoor");
83 |
84 | }
85 |
86 | /**
87 | * Custom forms - click the approve consent checkbox.
88 | */
89 | public void approveConsent() {
90 | _botAction.waitOnElementAndClick(By.id("job_application_data_compliance_gdpr_consent_given"));
91 | }
92 |
93 | /**
94 | * Upload resume.
95 | *
96 | * @throws IOException Catch any file errors.
97 | */
98 | public void uploadResume() {
99 | _botAction.waitOnElementAndClick(By.cssSelector("a[data-source='paste']"));
100 |
101 | try {
102 | _botAction.tryToFindElement(By.id("resume_text"))
103 | .sendKeys(ExtractPDFText.extractPDFTextToString(new File(JobApplicationData.resumePath)));
104 | } catch (IOException e) {
105 | System.out.println("Error uploading resume");
106 | }
107 | }
108 |
109 | /**
110 | * Submit the application.
111 | */
112 | public void submitApplication() {
113 | _botAction.waitOnElementAndClick(By.id("submit-app"));
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/IndeedApplyBot.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.util.List;
4 |
5 | import org.openqa.selenium.By;
6 | import org.openqa.selenium.WebElement;
7 |
8 | import com.btieu.JobApplicationBot.JobApplicationData.ApplicationType;
9 |
10 | /**
11 | * An IndeedApplyBot is an IndeedBot. Define methods which will implement this
12 | * interface with lambda expressions.
13 | *
14 | * @author Bruce Tieu
15 | *
16 | */
17 | public class IndeedApplyBot extends IndeedBot {
18 |
19 | private JobApplicationData _jobAppData;
20 | private JobApplicationData.ApplicationType _appType;
21 |
22 | /**
23 | * Parameterized constructor to initialize JobApplicationData.
24 | *
25 | * @param jobAppData The JobApplicationData object.
26 | * @param appType The enum holding application types.
27 | */
28 | public IndeedApplyBot(JobApplicationData jobAppData, ApplicationType appType) {
29 | super(jobAppData, appType);
30 | _jobAppData = jobAppData;
31 | _appType = appType;
32 |
33 | }
34 |
35 | /**
36 | * Find all the Indeed easy apply jobs on a given page.
37 | *
38 | * @param index The particular index in the list of jobs.
39 | * @param jobList The list of all jobs.
40 | */
41 | public void saveEasyApplyJobs(int index, List jobList) {
42 |
43 | boolean isEasyApply = jobList.get(index).findElements(By.className("iaLabel")).size() > 0;
44 |
45 | if (isEasyApply) {
46 | String jobLink = getJobViewLink(index, jobList);
47 | System.out.println(jobLink);
48 | clickOnApplyButton();
49 | saveEZApplyJob(jobLink, _appType);
50 | }
51 | }
52 |
53 | /**
54 | * Skip easy apply jobs - ie get the not easy apply jobs.
55 | *
56 | * @param index The particular index in the list of jobs.
57 | * @param jobList The list of all jobs.
58 | */
59 | public void saveNonEasyApplyJobs(int index, List jobList) {
60 |
61 | boolean isEasyApply = jobList.get(index).findElements(By.className("iaLabel")).size() > 0;
62 |
63 | if (!isEasyApply) {
64 |
65 | String jobLink = getJobViewLink(index, jobList);
66 | jobLink = jobLink.replace("viewjob", "rc/clk");
67 | jobLink = jobLink.replace("vjs", "assa");
68 | System.out.println(jobLink);
69 | saveJob(jobLink, _appType);
70 | }
71 | }
72 |
73 |
74 | /**
75 | * Find both easy apply and not easy apply jobs.
76 | *
77 | * @param index The particular index in the list of jobs.
78 | * @param jobList The list of all jobs.
79 | */
80 | public void saveAllJobs(int index, List jobList) {
81 |
82 | String jobLink = getJobViewLink(index, jobList);
83 | jobLink = jobLink.replace("viewjob", "rc/clk");
84 | jobLink = jobLink.replace("vjs", "assa");
85 | System.out.println(jobLink);
86 | saveJob(jobLink, _appType);
87 | }
88 | }
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/IndeedBot.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.IOException;
4 | import java.text.SimpleDateFormat;
5 | import java.util.Date;
6 | import java.util.List;
7 |
8 | import org.openqa.selenium.By;
9 | import org.openqa.selenium.Keys;
10 | import org.openqa.selenium.WebElement;
11 |
12 | /**
13 | * An IndeedBot is a type of Bot with different functions for navigating an Indeed site.
14 | *
15 | * @author Bruce Tieu
16 | */
17 | public class IndeedBot extends Bot {
18 |
19 | private JobApplicationData _jobAppData;
20 | private JobApplicationData.ApplicationType _appType;
21 | private String _parentWindow;
22 |
23 | /**
24 | * This is a class constructor which initializes job application data, job
25 | * application type.
26 | *
27 | * @param jobAppData The object which holds job application data.
28 | * @param appType The enum type which is a set of application types.
29 | */
30 | public IndeedBot(JobApplicationData _jobAppData, JobApplicationData.ApplicationType _appType) {
31 | this._jobAppData = _jobAppData;
32 | this._appType = _appType;
33 | }
34 |
35 | /**
36 | * Navigate to the Indeed site.
37 | */
38 | public void navigateToJobPage() {
39 | getWebDriver().get(this._jobAppData.platformUrl);
40 | }
41 |
42 | /**
43 | * This method searches for jobs based on job position name and location.
44 | *
45 | * @throws InterruptedException Catch errors if element is not found.
46 | */
47 | public void searchJobs() {
48 |
49 | // Click on the find jobs tab
50 | waitOnElementAndClick(By.className("gnav-PageLink-text"));
51 |
52 | // Locate the "What" and "Where" input fields.
53 | tryToFindElement(By.id("text-input-what")).clear();
54 | tryToFindElementAndSendKeys(By.id("text-input-what"), this._jobAppData.whatJob);
55 |
56 | // Clear the "Where" field and send in the location of the job.
57 | getActions().sendKeys(Keys.TAB);
58 | getActions().sendKeys(Keys.DELETE);
59 | getActions().build().perform();
60 |
61 | tryToFindElementAndSendKeys(By.id("text-input-where"),this._jobAppData.locationOfJob).submit();
62 |
63 | }
64 |
65 |
66 | /**
67 | * Click on the easy apply button on indeed.
68 | */
69 | public void clickOnApplyButton() {
70 | // Wait until the following elements to appear before clicking on it.
71 | waitOnElementAndClick(By.id("indeedApplyButtonContainer"));
72 | switchIframes(By.cssSelector("iframe[title='Job application form container']"));
73 | switchIframes(By.cssSelector("iframe[title='Job application form']"));
74 | }
75 |
76 | /**
77 | * Save each easy apply job to a container.
78 | *
79 | * @param jobLink The application page.
80 | * @param appType The type of application it was.
81 | */
82 | public void saveEZApplyJob(String jobLink, JobApplicationData.ApplicationType appType) {
83 |
84 | // Check if job has been applied to.
85 | boolean isApplied = _hasJobBeenAppliedTo();
86 |
87 | try {
88 | boolean containerHasJob = JobPostingData.jobPostingContainer.contains(_getJobInformation(jobLink, appType, isApplied));
89 |
90 | if (!isApplied) {
91 |
92 | if (!containerHasJob) {
93 | JobPostingData.jobPostingContainer.add(_getJobInformation(jobLink, appType, isApplied)); // Save job.
94 | getWebDriver().close(); // Close that new window (the job that was opened).
95 | getWebDriver().switchTo().window(_parentWindow); // Switch back to job lists window
96 | }
97 | }
98 | // Continue searching for jobs if already applied to.
99 | else {
100 | getWebDriver().close();
101 | getWebDriver().switchTo().window(_parentWindow);
102 | }
103 | } catch (Exception e) {}
104 | }
105 |
106 | /**
107 | * Save any type of job to a container.
108 | *
109 | * @param jobLink The application page.
110 | * @param appType The type of application it was.
111 | */
112 | public void saveJob(String jobLink, JobApplicationData.ApplicationType appType) {
113 |
114 | try {
115 | boolean containerHasJob = JobPostingData.jobPostingContainer.contains(_getJobInformation(jobLink, appType, false));
116 | // Add unique JobPostings to container.
117 | if (!containerHasJob) {
118 | JobPostingData.jobPostingContainer.add(_getJobInformation(jobLink, appType, false)); // Save job.
119 | getWebDriver().close(); // Close that new window (the job that was opened).
120 | getWebDriver().switchTo().window(_parentWindow); // Switch back to job listing window.
121 | }
122 | } catch (Exception e) {}
123 |
124 | }
125 |
126 | /**
127 | * Get the actual link of the job.
128 | *
129 | * @param index The index it's at in the list of job cards.
130 | * @return The link of the job.
131 | */
132 | public String getJobViewLink(int index, List jobList) {
133 | _parentWindow = getWebDriver().getWindowHandle(); // Get the current window.
134 | String href = jobList.get(index).findElement(By.className("jobtitle")).getAttribute("href"); // Get job link.
135 | href = href.replace("rc/clk", "viewjob");
136 | navigateToLinkInNewTab(href); // Open that job in a new tab.
137 | return href;
138 | }
139 |
140 | /**
141 | * This method gets information from the job description like job title and
142 | * company name.
143 | *
144 | * @param driver This is the web driver.
145 | * @param jobLink This is the link of the job of type string.
146 | * @param appType This is the application type of type string.
147 | * @param applied This is bool indicating whether or not the job has already
148 | * been applied to.
149 | * @return This returns a new JobPostingData object.
150 | * @throws IOException Catch file errors.
151 | */
152 | private JobPostingData _getJobInformation(String jobLink, JobApplicationData.ApplicationType appType,
153 | boolean applied) throws IOException {
154 |
155 | String remote, submitted;
156 |
157 | Date date = new Date();
158 | SimpleDateFormat formatter = new SimpleDateFormat("MM-dd-yyyy HH:mm");
159 |
160 | String jobTitle = tryToFindElement(By.className("jobsearch-JobInfoHeader-title")).getText();
161 | WebElement companyLocationDiv = getWebDriver()
162 | .findElement(By.className("jobsearch-DesktopStickyContainer-subtitle"));
163 | List nestedDiv = companyLocationDiv.findElements(By.tagName("div"));
164 | List innerDivs = nestedDiv.get(0).findElements(By.tagName("div"));
165 |
166 | String companyName = innerDivs.get(0).getText();
167 | String companyLoc = innerDivs.get(innerDivs.size() - 1).getText();
168 | String isRemote = nestedDiv.get(nestedDiv.size() - 1).getText().toLowerCase();
169 |
170 | if (isRemote.contains("remote"))
171 | remote = "yes";
172 | else
173 | remote = "no";
174 |
175 | if (applied)
176 | submitted = "yes";
177 | else
178 | submitted = "no";
179 |
180 | // Return a new JobPostingData object.
181 | return new JobPostingData(jobMatchScore(By.id("jobDescriptionText")), jobTitle, companyName, companyLoc, remote,
182 | formatter.format(date), appType.name(), jobLink, submitted, "");
183 | }
184 |
185 | /**
186 | * Check if a job has been applied to.
187 | *
188 | * @return True if applied, false otherwise.
189 | */
190 | private boolean _hasJobBeenAppliedTo() {
191 |
192 | // Check if job has been applied already.
193 | if (getWebDriver().findElements(By.id("ia_success")).size() > 0) {
194 | WebElement popUp = tryToFindElement(By.id("close-popup"));
195 | popUp.click();
196 | return true;
197 | } else {
198 | getActions().moveByOffset(0, 0).click().build().perform();
199 | getWebDriver().switchTo().defaultContent();
200 | return false;
201 | }
202 | }
203 |
204 |
205 | }
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/JobApplicationData.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | /**
4 | * This class holds all the job application data.
5 | *
6 | * @author Bruce Tieu
7 | */
8 | public class JobApplicationData {
9 | public String firstname, lastname, fullname, email, phone, school, location, currentCompany, linkedin, github, portfolio, password, platformUrl, whatJob,
10 | locationOfJob;
11 | public static String resumePath;
12 |
13 | public static enum ApplicationType {
14 | EASILY_APPLY, NOT_EASY_APPLY, ALL, LEVER_GREENHOUSE
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/JobIterator.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 |
6 | import org.openqa.selenium.NoSuchElementException;
7 | import org.openqa.selenium.WebElement;
8 |
9 | /**
10 | * Iterator class to iterate through a list of jobs.
11 | *
12 | * @author Bruce Tieu
13 | *
14 | */
15 | public class JobIterator {
16 | private Bot _bot;
17 | private WriteFiles _writeFiles;
18 | private JobApplicationData.ApplicationType _appType;
19 |
20 | /**
21 | * Parameterized constructor which initializes a Bot, appType, and WriteFiles
22 | * object.
23 | *
24 | * @param writeFiles The object to allow writing objects to files.
25 | * @param appType The application type.
26 | */
27 | public JobIterator(WriteFiles writeFiles, JobApplicationData.ApplicationType appType) {
28 | _bot = new Bot();
29 | _writeFiles = writeFiles;
30 | _appType = appType;
31 | }
32 |
33 | /**
34 | * Loop through the job list which is passed in.
35 | *
36 | * @param jobList The list of jobs on the page.
37 | * @param applyInterface Methods to apply to different types of jobs.
38 | * @param pagingInterface Methods for pagination.
39 | * @throws IOException Catch file errors.
40 | */
41 | public void loopThroughJob(List jobList, ApplyInterface applicationHandler,
42 | PagingInterface paginationHandler) {
43 |
44 | int currPageNum = 0;
45 | List tempList = jobList;
46 | int numOfJobs = tempList.size();
47 |
48 | // Loop through each of the job divs present on the page.
49 | int i = 0;
50 |
51 | while (i < numOfJobs) {
52 | try {
53 |
54 | boolean atLastCard = (i == tempList.size() - 1);
55 | boolean atLastPage = (currPageNum == JobPostingData.pagesToScrape);
56 |
57 | // Call the specific apply method here.
58 | applicationHandler.handleJob(i, tempList);
59 |
60 | // Stop at the last job listing & pagenum specified.
61 | if (atLastCard && atLastPage)
62 | break;
63 | // Go to the next page to continue saving jobs.
64 | if (atLastCard) {
65 | i = -1;
66 | currPageNum += 1;
67 |
68 | // Update the job list when specific paging function is called.
69 | tempList = paginationHandler.handlePage(currPageNum);
70 | }
71 | i++;
72 | } catch (NoSuchElementException e) {
73 | // If error, go to next index.
74 | System.out.println(e.getMessage());
75 | continue;
76 | }
77 | }
78 |
79 | // Write all jobs to excel file.
80 | if (_appType != JobApplicationData.ApplicationType.LEVER_GREENHOUSE) {
81 | try {
82 | _writeFiles.writeJobPostToCSV(JobPostingData.jobPostingContainer);
83 | System.out.println("Finished export");
84 | } catch (IOException e) {
85 | System.out.println("Could not write to csv file");
86 | }
87 | }
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/JobPostingData.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | /**
7 | * This class holds job posting data.
8 | *
9 | * @author bruce
10 | *
11 | */
12 | public class JobPostingData {
13 |
14 | public double jobMatchScore;
15 | public String jobTitle, companyName, companyLoc, remote, dateApplied, appType, jobLink, submitted, jobStatus;
16 |
17 | /**
18 | * Initialize job information strings.
19 | *
20 | * @param jobMatchScore The cosine similarity of two documents.
21 | * @param jobTitle The job title.
22 | * @param companyName The company name.
23 | * @param companyLoc The company location.
24 | * @param remote If the job is remote.
25 | * @param dateApplied The date the job was applied to.
26 | * @param appType The application type.
27 | * @param jobLink The link of the job.
28 | * @param submitted If the job was submitted or not.
29 | * @param jobStatus The job status.
30 | */
31 | public JobPostingData(double jobMatchScore, String jobTitle, String companyName, String companyLoc, String remote,
32 | String dateApplied, String appType, String jobLink, String submitted, String jobStatus) {
33 | this.jobMatchScore = jobMatchScore;
34 | this.jobTitle = jobTitle;
35 | this.companyName = companyName;
36 | this.companyLoc = companyLoc;
37 | this.remote = remote;
38 | this.dateApplied = dateApplied;
39 | this.appType = appType;
40 | this.jobLink = jobLink;
41 | this.submitted = submitted;
42 | this.jobStatus = jobStatus;
43 | }
44 |
45 | // Static variables.
46 | public static int pagesToScrape;
47 | public static List jobPostingContainer = new ArrayList();
48 |
49 | public JobPostingData() {
50 |
51 | }
52 |
53 | @Override
54 | /**
55 | * Print out object member variables.
56 | */
57 | public String toString() {
58 | StringBuilder builder = new StringBuilder();
59 | builder.append("JobPostingData [jobMatchScore=");
60 | builder.append(this.jobMatchScore);
61 | builder.append(", jobTitle=");
62 | builder.append(this.jobTitle);
63 | builder.append(", companyName=");
64 | builder.append(this.companyName);
65 | builder.append(", companyLoc=");
66 | builder.append(this.companyLoc);
67 | builder.append(", remote=");
68 | builder.append(this.remote);
69 | builder.append(", dateApplied=");
70 | builder.append(this.dateApplied);
71 | builder.append(", appType=");
72 | builder.append(this.appType);
73 | builder.append(", jobLink=");
74 | builder.append(this.jobLink);
75 | builder.append(", submitted=");
76 | builder.append(this.submitted);
77 | builder.append(", jobStatus=");
78 | builder.append(this.jobStatus);
79 | builder.append("]");
80 | return builder.toString();
81 | }
82 |
83 | // Needed these getters so that the JobPostingData object correct writes to CSV
84 | // via BeanWriter.
85 |
86 | /**
87 | * Get the cosine similarity value ie. how well the resume matches the job
88 | * description.
89 | *
90 | * @return The cosine similarity.
91 | */
92 | public double getjobMatchScore() {
93 | return this.jobMatchScore;
94 | }
95 |
96 | /**
97 | * Get the title of the job e.g Software Engineer.
98 | *
99 | * @return a job title of type string.
100 | */
101 | public String getJobTitle() {
102 | return this.jobTitle;
103 | }
104 |
105 | /**
106 | * Get the company name of the job.
107 | *
108 | * @return The company name of type string.
109 | */
110 | public String getCompanyName() {
111 | return this.companyName;
112 | }
113 |
114 | /**
115 | * Get the location of the job.
116 | *
117 | * @return The company name of type string.
118 | */
119 | public String getCompanyLoc() {
120 | return this.companyLoc;
121 | }
122 |
123 | /**
124 | * Get information about the job being remote or not.
125 | *
126 | * @return A string that is either "yes" or "no".
127 | */
128 | public String getRemote() {
129 | return this.remote;
130 | }
131 |
132 | /**
133 | * Get the date the job was applied to.
134 | *
135 | * @return The date formatted as a string.
136 | */
137 | public String getDateApplied() {
138 | return this.dateApplied;
139 | }
140 |
141 | /**
142 | * Get the application type e.g easily apply
143 | *
144 | * @return The application type as a string.
145 | */
146 | public String getAppType() {
147 | return this.appType;
148 | }
149 |
150 | /**
151 | * Get the link to the job application.
152 | *
153 | * @return A job link which is a string.
154 | */
155 | public String getJobLink() {
156 | return this.jobLink;
157 | }
158 |
159 | /**
160 | * Get information about whether or not the application had already been
161 | * submitted.
162 | *
163 | * @return A string of either "yes" or "no".
164 | */
165 | public String getSubmitted() {
166 | return this.submitted;
167 | }
168 |
169 | /**
170 | * Get the status of the application.
171 | *
172 | * @return An empty string.
173 | */
174 | public String getJobStatus() {
175 | return this.jobStatus;
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/LeverForms.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.util.List;
4 |
5 | import org.openqa.selenium.By;
6 | import org.openqa.selenium.NoSuchElementException;
7 | import org.openqa.selenium.WebElement;
8 |
9 | /**
10 | * Class to handle questions on lever forms.
11 | * @author Bruce Tieu
12 | *
13 | */
14 | public class LeverForms {
15 | private Bot _bot;
16 | private JobApplicationData _jobAppData;
17 | private JobApplicationData.ApplicationType _appType;
18 |
19 | /**
20 | * Instantiate Bot to access actions.
21 | */
22 | public LeverForms() {
23 | _bot = new Bot();
24 | }
25 |
26 | /**
27 | * Fill out basic applicant info.
28 | *
29 | * @param jobAppData The job application object.
30 | */
31 | public void fillAllBasicInfo(JobApplicationData jobAppData) {
32 | _bot.tryToFindElementAndSendKeys(By.name("name"), jobAppData.fullname);
33 | _bot.tryToFindElementAndSendKeys(By.name("email"), jobAppData.email);
34 | _bot.tryToFindElementAndSendKeys(By.name("phone"), jobAppData.phone);
35 | _bot.tryToFindElementAndSendKeys(By.name("org"), jobAppData.currentCompany); // current company
36 | _bot.tryToFindElementAndSendKeys(By.name("urls[LinkedIn]"), jobAppData.linkedin);
37 | _bot.tryToFindElementAndSendKeys(By.name("urls[GitHub]"), jobAppData.github);
38 | _bot.tryToFindElementAndSendKeys(By.name("urls[Portfolio]"), jobAppData.portfolio); // portfolio
39 | }
40 |
41 | /**
42 | * Fill out How did you find us?
43 | */
44 | public void fillAllHowDidYouFindUs() {
45 | _bot.tryToSelectFromDpn(By.tagName("select"), "Glassdoor");
46 | }
47 |
48 | /**
49 | * Upload resume.
50 | */
51 | public void uploadResume() {
52 | try {
53 | _bot.getWebDriver().findElement(By.name("resume")).sendKeys(JobApplicationData.resumePath);
54 | } catch (NoSuchElementException e) {
55 | System.out.println("Error uploading resume");
56 | }
57 | }
58 |
59 | /**
60 | * Submit Application.
61 | */
62 | public void submitApplication() {
63 | _bot.waitOnElementAndClick(By.className("template-btn-submit"));
64 |
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/LeverGreenhouseBot.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 |
6 | import org.openqa.selenium.By;
7 | import org.openqa.selenium.WebElement;
8 |
9 | import com.btieu.JobApplicationBot.JobApplicationData.ApplicationType;
10 |
11 | /**
12 | * A lot of the not easy apply jobs on Glassdoor are Greenhouse or Lever links,
13 | * so this class will apply to those jobs.
14 | *
15 | * @author Bruce Tieu
16 | *
17 | */
18 | public class LeverGreenhouseBot extends GlassdoorBot {
19 |
20 | private JobApplicationData.ApplicationType _appType;
21 | private JobApplicationData _jobAppData;
22 | private WriteFiles _writeFiles;
23 | private LeverForms _leverForms;
24 | private GreenhouseForms _greenhouseForms;
25 |
26 | /**
27 | * Parameterized constructor which initializes JobApplicationData, WriteFiles,
28 | * and Form objects.
29 | *
30 | * @param jobAppData The JobApplicationData object.
31 | * @param appType The job application type enum.
32 | * @param writeFiles The writing files object.
33 | */
34 | public LeverGreenhouseBot(JobApplicationData jobAppData, ApplicationType appType, WriteFiles writeFiles) {
35 | super(jobAppData, appType);
36 | _appType = appType;
37 | _jobAppData = jobAppData;
38 | _writeFiles = writeFiles;
39 | _leverForms = new LeverForms();
40 | _greenhouseForms = new GreenhouseForms();
41 | }
42 |
43 | /**
44 | * Save the lever and greenhouse jobs without assigning a job match score.
45 | */
46 | public void saveLGJobs(int index, List jobList) {
47 | saveJob(_appType, jobList, index);
48 | }
49 |
50 | /**
51 | * Apply to Lever and Greenhouse jobs.
52 | *
53 | */
54 | public void apply() {
55 |
56 | System.out.println(JobPostingData.jobPostingContainer.size());
57 |
58 | for (JobPostingData jobPost : JobPostingData.jobPostingContainer) {
59 |
60 | boolean isLever = jobPost.jobLink.contains("lever");
61 | boolean isGreenhouse = jobPost.jobLink.contains("greenhouse");
62 |
63 | try {
64 | if (isLever) {
65 | System.out.println("Applying to lever job...");
66 | applyToLeverJobs(jobPost.jobLink);
67 | if (elementExists(By.xpath("//h3[@data-qa='msg-submit-success']"))) {
68 | System.out.println("Successfully applied");
69 | jobPost.submitted = "Yes";
70 | }
71 | }
72 | else if (isGreenhouse) {
73 | System.out.println("Applying to greenhouse job...");
74 | applyToGreenhouseJobs(jobPost.jobLink);
75 | if (elementExists(By.id("application_confirmation"))) {
76 | System.out.println("Successfully applied");
77 | jobPost.submitted = "Yes";
78 | }
79 | }
80 |
81 | } catch (Exception e) {
82 | continue;
83 | }
84 | }
85 | try {
86 | _writeFiles.writeJobPostToCSV(JobPostingData.jobPostingContainer);
87 | } catch (IOException e) {
88 | System.out.println(e.getMessage());
89 | }
90 | }
91 |
92 | /**
93 | * Apply to Lever jobs.
94 | *
95 | * @param leverLink The link to lever application.
96 | */
97 | public void applyToLeverJobs(String leverLink) {
98 | getWebDriver().get(leverLink);
99 | waitOnElementAndClick(By.className("template-btn-submit"));
100 |
101 | _leverForms.fillAllBasicInfo(_jobAppData);
102 | _leverForms.fillAllHowDidYouFindUs();
103 | _leverForms.uploadResume();
104 | _leverForms.submitApplication();
105 |
106 | }
107 |
108 | /**
109 | * Apply to Greenhouse jobs.
110 | *
111 | * @param greenhouseLink The link to the Greenhouse application.
112 | */
113 | public void applyToGreenhouseJobs(String greenhouseLink) {
114 | getWebDriver().get(greenhouseLink);
115 | waitOnElementAndClick(By.className("template-btn-submit"));
116 |
117 | _greenhouseForms.fillAllBasicInfo(_jobAppData);
118 | _greenhouseForms.fillAllWorkAuth();
119 | _greenhouseForms.fillAllHowDidYouFindUs();
120 | _greenhouseForms.approveConsent();
121 | _greenhouseForms.uploadResume();
122 | _greenhouseForms.submitApplication();
123 |
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/LinkedInBot.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.util.ArrayList;
4 | import java.util.LinkedList;
5 | import java.util.List;
6 | import java.util.Queue;
7 |
8 | import org.openqa.selenium.By;
9 | import org.openqa.selenium.WebElement;
10 |
11 | import net.codejava.swing.MessageDialog;
12 |
13 | /**
14 | * Linkedin connections bot. Connect with the people based off the "People You
15 | * May Know" or "People Who Also Viewed" sections in LinkedIn. Send a custom
16 | * message to each person to connect. Can connect on specific keywords.
17 | *
18 | * @author Bruce Tieu
19 | *
20 | */
21 | public class LinkedInBot extends Bot {
22 |
23 | private JobApplicationData _jobAppData;
24 | private LinkedInPerson _linkedInPerson;
25 |
26 | private List _visitedProfiles;
27 | private Queue _profilesToBeVisited;
28 |
29 | private static final String _CLASS_PYMK_SECTION = "pv-profile-pymk__container";
30 | private static final String _CLASS_PV_SECTION = "pv-browsemap-section";
31 | private static final String _CLASS_PYMK_MEMBERS = "pv-pymk-section__member-container";
32 | private static final String _CLASS_PV_MEMBERS = "pv-browsemap-section__member-container";
33 | private static final String _CLASS_PYMK_HEADLINE = "pv-pymk-section__member-headline";
34 | private static final String _CLASS_PV_HEADLINE = "pv-browsemap-section__member-headline";
35 | private static final String _CLASS_CONNECT_BTN = "pv-s-profile-actions--connect";
36 | private static final String _CLASS_ADD_NOTE_BTN = "artdeco-button--secondary";
37 | private static final String _ID_CUSTOM_MSG = "custom-message";
38 | private static final String _CLASS_MORE_BTN = "pv-s-profile-actions__overflow";
39 |
40 | /**
41 | * Parameterized constructor to initialize the job application data and
42 | * LinkedInPerson objects.
43 | *
44 | * @param jobAppData The job application data object.
45 | * @param linkedInPerson The LinkedInPerson object.
46 | */
47 | public LinkedInBot(JobApplicationData jobAppData, LinkedInPerson linkedInPerson) {
48 | _jobAppData = jobAppData;
49 | _linkedInPerson = linkedInPerson;
50 | _visitedProfiles = new ArrayList();
51 | _profilesToBeVisited = new LinkedList();
52 | }
53 |
54 | /**
55 | * Navigate to the job platform site.
56 | */
57 | public void navigateToJobPage() {
58 | getWebDriver().get(_jobAppData.platformUrl);
59 | }
60 |
61 | /**
62 | * This method logs in to the job site.
63 | *
64 | * @throws InterruptedException
65 | */
66 | public void login() {
67 |
68 | // Make sure the Email and Password fields are cleared out of any text.
69 | getWebDriver().findElement(By.id("username")).clear();
70 | getWebDriver().findElement(By.id("password")).clear();
71 |
72 | // Populate the fields with an email and a password
73 | tryToFindElementAndSendKeys(By.id("username"), _jobAppData.email);
74 | tryToFindElementAndSendKeys(By.id("password"), _jobAppData.password);
75 |
76 | waitOnElementAndClick(By.className("btn__primary--large"));
77 | }
78 |
79 | /**
80 | * Go to your Linkedin profile, need to access the "People Also Viewed" and
81 | * "People You May Know" sections.
82 | */
83 | public void goToProfile() {
84 | getWebDriver().get(_jobAppData.linkedin);
85 | }
86 |
87 | /**
88 | * Get all the profiles links from the "People Also Viewed" and "People You May
89 | * Know" sections.
90 | */
91 | public void aggregatePeopleProfiles() {
92 | _getPeopleYouMayKnow();
93 | _getPeopleViewed();
94 |
95 | }
96 |
97 | /**
98 | * Connect with profiles you have visited.
99 | */
100 | public void connect() {
101 |
102 | // Connection requests counter.
103 | int connections = 0;
104 |
105 | // While the list of profiles to be visited is not empty...
106 | while (_profilesToBeVisited != null && !_profilesToBeVisited.isEmpty()) {
107 |
108 | // Get the first profile in the queue.
109 | LinkedInPerson queuedProfile = _profilesToBeVisited.poll();
110 |
111 | try {
112 |
113 | // Connect with people with the keywords you're looking for.
114 | if (_containsKeywords(queuedProfile.occupation.toLowerCase())) {
115 |
116 | // Add the profile to the visited list.
117 | getWebDriver().get(queuedProfile.profileLink);
118 | _visitedProfiles.add(queuedProfile.profileLink);
119 |
120 | // Keep updating the list of profiles to visit.
121 | if (elementExists(By.className(_CLASS_PYMK_SECTION))
122 | && elementExists(By.className(_CLASS_PV_SECTION))) {
123 | aggregatePeopleProfiles();
124 | }
125 |
126 | // Write connection message.
127 | try {
128 | _easyConnectRequest(queuedProfile.message);
129 | } catch (Exception e) {
130 | _hardConnectRequest(queuedProfile.message);
131 | }
132 |
133 | // Send message.
134 | if (isClicked(By.className("ml1"))) {
135 | connections += 1;
136 | System.out.println("Sent invitation!");
137 | }
138 |
139 | // Stop after connecting with x number of people.
140 | if (connections == LinkedInPerson.MAX_CONNECTIONS) {
141 | MessageDialog.infoBox(MessageDialog.SUCCESSFUL_CONNECT_REQUEST, MessageDialog.SUCCESS_TITLE);
142 | break;
143 | }
144 | }
145 |
146 | } catch (Exception e) {
147 | System.out.println("Some error");
148 | }
149 | }
150 |
151 | // If no results, then output a message to GUI.
152 | if (_visitedProfiles.isEmpty()) {
153 | MessageDialog.infoBox(MessageDialog.LINKEDIN_NO_RESULTS_MSG, MessageDialog.INVALID_KEYWORD_TITLE);
154 | return;
155 | }
156 | }
157 |
158 | /**
159 | * Get the list of people under "People Also Viewed Section"
160 | */
161 | private void _getPeopleViewed() {
162 |
163 | WebElement pvContainer = tryToFindElement(By.className(_CLASS_PV_SECTION));
164 | List pvList = pvContainer.findElements(By.className(_CLASS_PV_MEMBERS));
165 |
166 | _addToProfilesToBeVisited(By.className(_CLASS_PV_HEADLINE), pvList);
167 |
168 | for (LinkedInPerson p : _profilesToBeVisited) {
169 | System.out.println(p.firstname + ", " + p.profileLink + ", " + p.occupation + ", " + p.message);
170 | }
171 | }
172 |
173 | /**
174 | * Get list of people under "People Who You May Know".
175 | */
176 | private void _getPeopleYouMayKnow() {
177 | WebElement pymkContainer = tryToFindElement(By.className(_CLASS_PYMK_SECTION));
178 | List pymkList = pymkContainer.findElements(By.className(_CLASS_PYMK_MEMBERS));
179 |
180 | _addToProfilesToBeVisited(By.className(_CLASS_PYMK_HEADLINE), pymkList);
181 |
182 | }
183 |
184 | /**
185 | * Fill the profiles to be visited.
186 | *
187 | * @param by The element which contains the people occupations.
188 | * @param peopleList The list of people from a specific section.
189 | */
190 | private void _addToProfilesToBeVisited(By by, List peopleList) {
191 |
192 | for (WebElement people : peopleList) {
193 | String profileLink = people.findElement(By.tagName("a")).getAttribute("href");
194 | String name = people.findElement(By.className("name")).getText();
195 | String occupation = people.findElement(by).getText();
196 |
197 | // Only add to profilesToBeVisited if it the profile isn't already in the list
198 | // and it hasn't been visited.
199 | if (!_profilesToBeVisited.stream().anyMatch(l -> l.profileLink.equals(profileLink))
200 | && !_visitedProfiles.contains(profileLink)) {
201 | _profilesToBeVisited.add(new LinkedInPerson(_splitFullname(name), profileLink, occupation,
202 | _assembleMessage(_splitFullname(name))));
203 | }
204 | }
205 | }
206 |
207 | /**
208 | * Get first name and capitalize it.
209 | *
210 | * @param name A person's full name.
211 | * @return The first name.
212 | */
213 | private String _splitFullname(String name) {
214 | String[] splitted = name.toLowerCase().split("\\s+");
215 | String firstName = Character.toUpperCase(splitted[0].charAt(0)) + splitted[0].substring(1);
216 | return firstName;
217 |
218 | }
219 |
220 | /**
221 | * Assemble personalized message.
222 | *
223 | * @param name The first name of the person.
224 | * @return The message.
225 | */
226 | private String _assembleMessage(String name) {
227 | String message = "Hi " + name + ", " + _linkedInPerson.message + _jobAppData.fullname + ".";
228 | return message;
229 | }
230 |
231 |
232 | /**
233 | * Check if their occupation contains the keywords.
234 | *
235 | * @param description Their header description.
236 | * @return True, if there's a match, false otherwise.
237 | */
238 | private boolean _containsKeywords(String description) {
239 |
240 | String[] splittedKeywords = _linkedInPerson.keywords.toLowerCase().split("\\s*,\\s*");
241 |
242 | for (int i = 0; i < splittedKeywords.length; i++) {
243 | if (description.contains(splittedKeywords[i])) {
244 | return true;
245 | }
246 | }
247 | return false;
248 | }
249 |
250 | /**
251 | * Handle cases where there's a "Connect" button.
252 | *
253 | * @param message The connect message.
254 | */
255 | private void _easyConnectRequest(String message) {
256 | getWebDriver().findElement(By.className(_CLASS_CONNECT_BTN)).click(); // click on Connect
257 | getWebDriver().findElement(By.className(_CLASS_ADD_NOTE_BTN)).click(); // Click on "Add a note"
258 | WebElement textarea = tryToFindElement(By.id(_ID_CUSTOM_MSG));
259 | textarea.sendKeys(message);
260 | }
261 |
262 | /**
263 | * Handle cases where you have to do more work to connect.
264 | *
265 | * @param message The connect message.
266 | */
267 | private void _hardConnectRequest(String message) {
268 | WebElement more = getWebDriver().findElement(By.className(_CLASS_MORE_BTN));
269 | more.click();
270 | more.findElement(By.className(_CLASS_CONNECT_BTN)).click();
271 | waitOnElementAndClick(By.className(_CLASS_ADD_NOTE_BTN)); // Click on "Add a note"
272 | WebElement textarea = tryToFindElement(By.id(_ID_CUSTOM_MSG));
273 | textarea.sendKeys(message);
274 | }
275 |
276 | }
277 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/LinkedInPerson.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | /**
4 | * Assemble a Linkedin Profile / person.
5 | * @author Bruce Tieu
6 | *
7 | */
8 | public class LinkedInPerson {
9 | public String firstname, profileLink, occupation, message, keywords;
10 | public static int MAX_CONNECTIONS;
11 |
12 | /**
13 | * Initialize name, profile link, occupation, and the message.
14 | * @param firstname The person's first name.
15 | * @param profileLink Their profile link.
16 | * @param occupation Their occupation.
17 | * @param message The message to be sent.
18 | */
19 | public LinkedInPerson(String firstname, String profileLink, String occupation, String message) {
20 | this.firstname = firstname;
21 | this.profileLink = profileLink;
22 | this.occupation = occupation;
23 | this.message = message;
24 | }
25 |
26 | /**
27 | * Default constructor.
28 | */
29 | public LinkedInPerson() {
30 |
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/Pagination.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.util.List;
4 |
5 | import org.openqa.selenium.By;
6 | import org.openqa.selenium.WebElement;
7 |
8 | /**
9 | * Define methods to click on next pages and implement this interface with a
10 | * lambda expression.
11 | *
12 | * @author Bruce Tieu
13 | *
14 | */
15 | public class Pagination {
16 | private JobApplicationData _jobAppData;
17 | private Bot _bot;
18 |
19 | /**
20 | * Parameterized constructor to initialize JobApplicationData and Bot object.
21 | *
22 | * @param jobAppData The JobApplicationData.
23 | */
24 | public Pagination(JobApplicationData jobAppData) {
25 | _jobAppData = jobAppData;
26 | _bot = new Bot();
27 | }
28 |
29 | /**
30 | * Go to next page listing in Indeed.
31 | *
32 | * @param pageNum The desired number of page numbers to search.
33 | * @return An updated list of jobs to be iterated over again.
34 | */
35 | public List goToNextIndeedPage(int pageNum) {
36 |
37 | String nextPageUrl = "https://www.indeed.com/jobs?q=" + _jobAppData.whatJob + "&l=" + _jobAppData.locationOfJob
38 | + "&start=" + pageNum * 10;
39 | System.out.println("Continuing search on next page...");
40 | _bot.getWebDriver().get(nextPageUrl);
41 |
42 | // Click out of potential pop ups.
43 | _bot.getActions().moveByOffset(0, 0).click().build().perform();
44 | _bot.getWebDriver().switchTo().defaultContent();
45 |
46 | return _bot.tryToFindElements(By.className("jobsearch-SerpJobCard"));
47 | }
48 |
49 | /**
50 | * Click on the next page on Glassdoor.
51 | *
52 | * @param pageNum The page number to go to.
53 | * @return An updated list of jobs to be iterated over again.
54 | */
55 | public List goToNextGlassdoorPage(int pageNum) {
56 |
57 | String pageUrl = _bot.getRequestURL(_bot.getWebDriver().getCurrentUrl());
58 | String newPageNum = "_IP" + Integer.toString(pageNum + 1) + ".htm";
59 | String newPageUrl = pageUrl.replace(".htm", newPageNum);
60 | System.out.println("Continuing search on next page...");
61 | _bot.getWebDriver().get(newPageUrl);
62 | return _bot.tryToFindElements(By.className("react-job-listing"));
63 |
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/PagingInterface.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.util.List;
4 |
5 | import org.openqa.selenium.By;
6 | import org.openqa.selenium.WebElement;
7 |
8 | @FunctionalInterface
9 | public interface PagingInterface {
10 | /**
11 | * Abstract method that is implemented by a lamda expression.
12 | *
13 | * @param pageNum The page number to search for jobs.
14 | * @return A list of jobs of type WebElement.
15 | */
16 | public List handlePage(int pageNum);
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/SingletonDriver.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.util.concurrent.TimeUnit;
4 |
5 | import org.openqa.selenium.chrome.ChromeDriver;
6 | import org.openqa.selenium.chrome.ChromeOptions;
7 | import org.openqa.selenium.interactions.Actions;
8 | import org.openqa.selenium.support.ui.WebDriverWait;
9 | import org.openqa.selenium.WebDriver;
10 |
11 |
12 | /**
13 | * Singleton class - allow only one instance of this class to be created at a time.
14 | *
15 | * @author bruce
16 | */
17 | public class SingletonDriver {
18 |
19 | private static SingletonDriver singleInstance = null;
20 |
21 | private static final String _CHROME_DRIVER_PROPERTY = "webdriver.chrome.driver";
22 | private static final String _CHROME_DRIVER_PATH = "/Applications/chromedriver";
23 | public static final int _MAX_WAIT_TIME = 10;
24 |
25 | private WebDriver _webDriver;
26 | private WebDriverWait _wait;
27 | private ChromeOptions _chromeOptions;
28 | private Actions _actions;
29 |
30 | /**
31 | * This the default constructor which initializes all the required drivers and
32 | * chrome options.
33 | */
34 | private SingletonDriver() {
35 | System.setProperty(_CHROME_DRIVER_PROPERTY, _CHROME_DRIVER_PATH);
36 | _chromeOptions = new ChromeOptions();
37 | _chromeOptions.addArguments("--disable-blink-features");
38 | _chromeOptions.addArguments("--disable-blink-features=AutomationControlled");
39 | _chromeOptions.addArguments("--window-size=1920,1080");
40 | _chromeOptions.addArguments("--disable-extensions");
41 | _chromeOptions.addArguments("--proxy-server='direct://'");
42 | _chromeOptions.addArguments("--proxy-bypass-list=*");
43 | _chromeOptions.addArguments("--start-maximized");
44 | _chromeOptions.addArguments("--headless");
45 | _chromeOptions.addArguments("--disable-gpu");
46 | _chromeOptions.addArguments("--disable-dev-shm-usage");
47 | _chromeOptions.addArguments("--no-sandbox");
48 | _chromeOptions.addArguments("--ignore-certificate-errors");
49 | _webDriver = new ChromeDriver(_chromeOptions);
50 | _webDriver.manage().timeouts().implicitlyWait(_MAX_WAIT_TIME, TimeUnit.SECONDS);
51 | _wait = new WebDriverWait(_webDriver, _MAX_WAIT_TIME);
52 | _actions = new Actions(_webDriver);
53 |
54 | }
55 |
56 | /**
57 | * Ensure only one instance is created.
58 | * @return The instance.
59 | */
60 | public static SingletonDriver getInstance() {
61 | if (singleInstance == null) {
62 | singleInstance = new SingletonDriver();
63 | }
64 | return singleInstance;
65 | }
66 |
67 | /**
68 | * This is a getter method.
69 | *
70 | * @return The driver object.
71 | */
72 | public WebDriver getWebDriver() {
73 | return _webDriver;
74 | }
75 |
76 | /**
77 | * This is a getter method.
78 | *
79 | * @return The wait object.
80 | */
81 | public WebDriverWait getWait() {
82 | return _wait;
83 | }
84 |
85 | /**
86 | * This is a getter method.
87 | *
88 | * @return The actions object.
89 | */
90 | public Actions getActions() {
91 | return _actions;
92 | }
93 |
94 | }
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/java/com/btieu/JobApplicationBot/WriteFiles.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.File;
4 | import java.io.FileWriter;
5 | import java.io.IOException;
6 | import java.io.Writer;
7 | import java.nio.file.Paths;
8 | import java.util.List;
9 |
10 | import org.supercsv.cellprocessor.constraint.NotNull;
11 | import org.supercsv.cellprocessor.ift.CellProcessor;
12 | import org.supercsv.io.CsvBeanWriter;
13 | import org.supercsv.io.ICsvBeanWriter;
14 | import org.supercsv.prefs.CsvPreference;
15 |
16 | import java.nio.file.Files;
17 |
18 |
19 | /**
20 | * Class which writes information to files.
21 | *
22 | * @author Bruce Tieu
23 | *
24 | */
25 | public class WriteFiles {
26 |
27 | private Writer _writer;
28 | private File _file;
29 | private String _filename = "";
30 |
31 |
32 | /**
33 | * Initialize file name, file, and writer objects.
34 | * @param filename The name of csv file.
35 | * @throws IOException Catch any file IO errors.
36 | */
37 | public WriteFiles(String filename) throws IOException {
38 | _filename = filename;
39 | _file = new File(_filename);
40 | _writer = new FileWriter(_file);
41 | }
42 |
43 | /**
44 | * This method writes a job post to a csv file.
45 | *
46 | * @param jobPosts A set of JobPostingData.
47 | * @return A string of the file contents.
48 | * @throws IOException Catch any file writing errors.
49 | */
50 | public String writeJobPostToCSV(List jobPosts) throws IOException {
51 | ICsvBeanWriter beanWriter = null;
52 | final String[] header = {"jobMatchScore", "jobTitle", "companyName", "companyLoc", "remote", "dateApplied", "appType",
53 | "jobLink", "submitted", "jobStatus" };
54 |
55 | try {
56 | beanWriter = new CsvBeanWriter(_writer, CsvPreference.STANDARD_PREFERENCE);
57 | final CellProcessor[] processors = getProcessors();
58 | beanWriter.writeHeader(header);
59 |
60 | for (JobPostingData jobPost : jobPosts) {
61 | beanWriter.write(jobPost, header, processors);
62 | }
63 |
64 | } catch (IOException e) {
65 | System.err.println("Error writing to CSV file: " + e);
66 | } finally {
67 | if (beanWriter != null) {
68 | try {
69 | beanWriter.close();
70 | } catch (IOException e) {
71 | System.err.println("Error closing the writer: " + e);
72 | }
73 | }
74 | }
75 |
76 | // Return the contents of what was written to the file in a string.
77 | return new String(Files.readAllBytes(Paths.get(_filename)));
78 |
79 | }
80 |
81 | /**
82 | * Sets up the processors. There are 10 columns in the CSV, so 10 processors are
83 | * defined.
84 | *
85 | * @return The cell processors.
86 | */
87 | private static CellProcessor[] getProcessors() {
88 | final CellProcessor[] processors = new CellProcessor[] { new NotNull(), // jobMatchScore
89 | new NotNull(), // jobTitle
90 | new NotNull(), // companyName
91 | new NotNull(), // companyLocation
92 | new NotNull(), // remote
93 | new NotNull(), // dateApplied
94 | new NotNull(), // jobType
95 | new NotNull(), // jobLink
96 | new NotNull(), // submitted
97 | new NotNull() // jobStatus
98 | };
99 |
100 | return processors;
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/parser/com/btieu/JobApplicationBot/CosineSimilarity.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * Class which computes the cosine similarity of two documents.
7 | *
8 | * @author bruce
9 | *
10 | */
11 | public class CosineSimilarity {
12 |
13 | /**
14 | * This calculates the cosine similarity of two documents.
15 | *
16 | * @param docA The TextDocument object storing the text of the first document.
17 | * @param docA The TextDocument object storing the text of the second document.
18 | * @return The cosine similarity of two documents.
19 | * @throws IOException Catch any file errors.
20 | */
21 | public static double cosineSimilarity(TextDocument docA, TextDocument docB) throws IOException {
22 |
23 | // First, get the tf-idf of the TextDocuments.
24 | TFIDFCalc tfidfCalc = new TFIDFCalc(docA, docB);
25 | TFIDFResultContainer runTFIDF = tfidfCalc.runTFIDFCalc();
26 |
27 | double dotProduct = 0.0;
28 | double normA = 0.0;
29 | double normB = 0.0;
30 |
31 | // Then compute the cosine similarity between the documents.
32 | for (String word : runTFIDF.getTFIDFHashDocA().keySet()) {
33 | dotProduct += runTFIDF.getTFIDFHashDocA().get(word) * runTFIDF.getTFIDFHashDocB().get(word);
34 | normA += Math.pow(runTFIDF.getTFIDFHashDocA().get(word), 2);
35 | normB += Math.pow(runTFIDF.getTFIDFHashDocB().get(word), 2);
36 | }
37 | return normA != 0 || normB != 0 ? dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)) : 0.0;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/parser/com/btieu/JobApplicationBot/ExtractPDFText.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 |
4 | /**
5 | * Copyright 2020 Bruce Tieu
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
8 | * use this file except in compliance with the License. You may obtain a copy of
9 | * the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 | * License for the specific language governing permissions and limitations under
17 | * the License. This is a simple text extraction example to get started.
18 | *
19 | * Adapted from:
20 | * https://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/util/ExtractTextSimple.java?view=markup
21 | *
22 | * @author Tilman Hausherr
23 | */
24 |
25 | import java.io.File;
26 | import java.io.IOException;
27 | import java.util.ArrayList;
28 | import java.util.Arrays;
29 | import java.util.Iterator;
30 | import java.util.List;
31 |
32 | import org.apache.pdfbox.pdmodel.PDDocument;
33 | import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
34 | import org.apache.pdfbox.text.PDFTextStripper;
35 |
36 | public class ExtractPDFText {
37 |
38 | /**
39 | * This method extracts the words from a pdf resume.
40 | *
41 | * @param resumePath The path of the resume.
42 | * @return A list of words with all stop words removed from the resume.
43 | * @throws IOException if there are any errors reading the file.
44 | */
45 | public static List extractPDFText(File resumePath) throws IOException {
46 |
47 | try (PDDocument document = PDDocument.load(resumePath)) {
48 | AccessPermission ap = document.getCurrentAccessPermission();
49 | if (!ap.canExtractContent()) {
50 | throw new IOException("You do not have permission to extract text");
51 | }
52 |
53 | PDFTextStripper stripper = new PDFTextStripper();
54 |
55 | // This example uses sorting, but in some cases it is more useful to switch it
56 | // off,
57 | // e.g. in some files with columns where the PDF content stream respects the
58 | // column order.
59 | stripper.setSortByPosition(true);
60 |
61 | return parseText(stripper.getText(document));
62 | }
63 |
64 | }
65 |
66 | /**
67 | * Extract text from PDF to a string.
68 | *
69 | * @param resumePath The path of the resume.
70 | * @return The strings of all text in the resume.
71 | * @throws IOException Catch any file errors.
72 | */
73 | public static String extractPDFTextToString(File resumePath) throws IOException {
74 |
75 | try (PDDocument document = PDDocument.load(resumePath)) {
76 | AccessPermission ap = document.getCurrentAccessPermission();
77 | if (!ap.canExtractContent()) {
78 | throw new IOException("You do not have permission to extract text");
79 | }
80 |
81 | PDFTextStripper stripper = new PDFTextStripper();
82 | stripper.setSortByPosition(true);
83 |
84 | return stripper.getText(document);
85 | }
86 |
87 | }
88 |
89 | /**
90 | * This method extracts the words from text.
91 | *
92 | * @param text The text (most likely the job description on the job site).
93 | * @return A list of words with all stop words removed from the text.
94 | */
95 | public static List parseText(String text) {
96 | return splitText(text);
97 | }
98 |
99 | /**
100 | * This method splits a text by spaces.
101 | *
102 | * @param text The document.
103 | * @return a string of splitted words.
104 | */
105 | public static List splitText(String text) {
106 | String parsedText = text.replaceAll("[^-A-Za-z./\n\r\t\\+\\' ]+", "");
107 | String[] words = parsedText.toLowerCase().split("[\\s\\.\\/\\-]+");
108 | List finalWordList = new ArrayList(Arrays.asList(words));
109 | // Then, iterate through the list and remove any words with length <= 1.
110 | for (Iterator iter = finalWordList.iterator(); iter.hasNext();) {
111 | String word = iter.next();
112 | if (word.length() <= 1) {
113 | iter.remove();
114 | }
115 | }
116 | return finalWordList;
117 | }
118 |
119 |
120 | }
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/parser/com/btieu/JobApplicationBot/TFIDFCalc.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.IOException;
4 | import java.util.ArrayList;
5 | import java.util.Hashtable;
6 | import java.util.List;
7 |
8 | /**
9 | * Class which calculates the tf-idf of a document.
10 | *
11 | * @author bruce
12 | *
13 | */
14 | public class TFIDFCalc {
15 |
16 | private TextDocument _textDocumentA, _textDocumentB;
17 |
18 | /**
19 | * This initializes two TextDocument objects so that the tf-idf can be
20 | * calculated between them.
21 | *
22 | * @param textDocumentA The first document.
23 | * @param textDocumentB The second document.
24 | * @throws IOException Catch any file errors.
25 | */
26 | public TFIDFCalc(TextDocument textDocumentA, TextDocument textDocumentB) throws IOException {
27 | _textDocumentA = textDocumentA;
28 | _textDocumentB = textDocumentB;
29 | }
30 |
31 | /**
32 | * This computes the term frequency of each word.
33 | *
34 | * @param listOfWords The list of words for each document (w/o union).
35 | * @return A word frequency hash table.
36 | */
37 | public Hashtable computeTF(List listOfWords) {
38 |
39 | Hashtable tfHash = new Hashtable();
40 |
41 | int termsInDoc = listOfWords.size();
42 |
43 | // Compute the term frequency of each word.
44 | // TF(t) = (Number of times term t appears in a document) / (Total number of
45 | // terms in the document).
46 | for (String word : _textDocumentA.getFrequencyByWord(listOfWords).keySet()) {
47 | tfHash.put(word, ((double) _textDocumentA.getFrequencyByWord(listOfWords).get(word) / (double) termsInDoc));
48 |
49 | }
50 |
51 | return tfHash;
52 | }
53 |
54 | /**
55 | * This computes the inverse document frequency of a word.
56 | *
57 | * @return A idf hash table.
58 | */
59 | public Hashtable computeIDF() {
60 |
61 | Hashtable idfHash = new Hashtable();
62 |
63 | // Create a list of hash tables.
64 | List> listOfHashes = new ArrayList>();
65 |
66 | // Add the frequency tables of words of each document to the list.
67 | listOfHashes.add(_textDocumentA.getFrequencyByWord());
68 | listOfHashes.add(_textDocumentB.getFrequencyByWord());
69 |
70 | int numOfDocuments = 2;
71 |
72 | // Set all values in the idf hash table to be 0.
73 | for (String word : listOfHashes.get(0).keySet()) {
74 | idfHash.put(word, 0.0);
75 | }
76 |
77 | // Count the number of documents with a term t (word) in it.
78 | for (Hashtable hashtable : listOfHashes) {
79 | for (String word : hashtable.keySet()) {
80 | if (hashtable.get(word) > 0) {
81 | idfHash.put(word, (double) idfHash.get(word) + 1);
82 | }
83 | }
84 | }
85 |
86 | // IDF(t) = ln(Total number of documents / Number of documents with term t in
87 | // it).
88 | for (String word : idfHash.keySet()) {
89 | double numDocWithTermT = idfHash.get(word);
90 | double idf = Math.log((double) numOfDocuments / numDocWithTermT);
91 |
92 | // Replace divide by 0 errors with a 0.
93 | if (numDocWithTermT == 0)
94 | idfHash.put(word, 0.0);
95 | else
96 | idfHash.put(word, idf);
97 |
98 | }
99 |
100 | return idfHash;
101 | }
102 |
103 | /**
104 | * Calculate the tf-idf of a document.
105 | *
106 | * @param tf The term frequency hash table.
107 | * @param idf The inverse document frequency hash table.
108 | * @return A tf-idf hash table.
109 | *
110 | */
111 | public Hashtable computeTFIDF(Hashtable tf) {
112 | Hashtable tfidfHash = new Hashtable();
113 |
114 | // tfidf(t, d, D) = tf(t,d) + tf(t,d) * idf(t, D).
115 | for (String word : tf.keySet()) {
116 | tfidfHash.put(word, tf.get(word) + (tf.get(word) * computeIDF().get(word)));
117 | }
118 |
119 | return tfidfHash;
120 | }
121 |
122 | /**
123 | * This method executes all previous methods in the class.
124 | *
125 | * @return A TFIDFResultContainer which passes two hash tables.
126 | * @throws IOException Checks if there are any file reading errors.
127 | *
128 | */
129 | public TFIDFResultContainer runTFIDFCalc() throws IOException {
130 |
131 | // Compute the term frequency of each document.
132 | Hashtable tfDocA = computeTF(_textDocumentA.getWordsFromDocument());
133 | Hashtable tfDocB = computeTF(_textDocumentB.getWordsFromDocument());
134 |
135 | // Create tf-idf vectors of each document.
136 | Hashtable tfidfDocA = computeTFIDF(tfDocA);
137 | Hashtable tfidfDocB = computeTFIDF(tfDocB);
138 |
139 | return new TFIDFResultContainer(tfidfDocA, tfidfDocB);
140 |
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/parser/com/btieu/JobApplicationBot/TFIDFResultContainer.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 |
4 | import java.util.Hashtable;
5 |
6 | /**
7 | * This class stores the result of the tf-idf computations in TFIDFCalc as an
8 | * object.
9 | *
10 | * @author Bruce Tieu
11 | *
12 | */
13 | public class TFIDFResultContainer {
14 |
15 | private Hashtable _tfidfDocA, _tfidfDocB;
16 |
17 | /**
18 | * This constructor initializes two tf-idf hash tables.
19 | *
20 | * @param _tfidfDocA The first document's tf-idf hash table.
21 | * @param _tfidfDocB The second document's tf-idf hash table.
22 | */
23 | public TFIDFResultContainer(Hashtable tfidfDocA, Hashtable tfidfDocB) {
24 | _tfidfDocA = tfidfDocA;
25 | _tfidfDocB = tfidfDocB;
26 | }
27 |
28 | /**
29 | * This gets the tf-idf hash table for the first document.
30 | *
31 | * @return a hash table.
32 | */
33 | public Hashtable getTFIDFHashDocA() {
34 | return _tfidfDocA;
35 | }
36 |
37 | /**
38 | * This gets the tf-idf hash table for the second document.
39 | *
40 | * @return a hash table.
41 | */
42 | public Hashtable getTFIDFHashDocB() {
43 | return _tfidfDocB;
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/main/parser/com/btieu/JobApplicationBot/TextDocument.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.HashSet;
6 | import java.util.Hashtable;
7 | import java.util.List;
8 | import java.util.Set;
9 |
10 | /**
11 | * A text document class to store the text of a pdf file or web page.
12 | *
13 | * @author bruce
14 | *
15 | */
16 | public class TextDocument {
17 |
18 | private List _wordsFromDocument; // The list of words for document A and document B.
19 | private static Set _union = new HashSet<>(); // Store the union of the two lists of words.
20 |
21 | /**
22 | * Constructor which stores PDF file as a TextDocument object.
23 | *
24 | * @param resumeFile The path to the resume.
25 | * @throws IOException Catch any file errors.
26 | */
27 | public TextDocument(File resumeFile) throws IOException {
28 | _wordsFromDocument = ExtractPDFText.extractPDFText(resumeFile);
29 | _union.addAll(_wordsFromDocument); // Add list of words to set.
30 | }
31 |
32 | /**
33 | * Constructor which stores text from the web-page as a TextDocument object.
34 | *
35 | * @param text The text from the web page.
36 | */
37 | public TextDocument(String text) {
38 | _wordsFromDocument = ExtractPDFText.parseText(text);
39 | _union.addAll(_wordsFromDocument); // Add list of words to set.
40 | }
41 |
42 | /**
43 | * Get the list of words from a document.
44 | *
45 | * @return a list of words.
46 | */
47 | public List getWordsFromDocument() {
48 | return _wordsFromDocument;
49 | }
50 |
51 | /**
52 | * This method computes the frequency of words.
53 | *
54 | * @return a hash table with the key being the word and the value being the
55 | * frequency of that word.
56 | */
57 | public Hashtable getFrequencyByWord() {
58 |
59 | Hashtable freqUniqueWords = new Hashtable();
60 |
61 | // Set the frequency of all words to be 0.
62 | for (String unionVal : _union) {
63 | freqUniqueWords.put(unionVal, 0.0);
64 | }
65 |
66 | // Count how many times the word appears in the cleanedList, populate those
67 | // counts as values in the hash table.
68 | for (String word : getWordsFromDocument()) {
69 | freqUniqueWords.put(word, freqUniqueWords.get(word) + 1);
70 | }
71 |
72 | return freqUniqueWords;
73 | }
74 |
75 | /**
76 | * This overloaded method computes the frequency of words in a document.
77 | *
78 | * @param cleanedList The list of words to be counted.
79 | * @return a hash table with the key being the word and the value being the
80 | * frequency of that word.
81 | */
82 | public Hashtable getFrequencyByWord(List cleanedList) {
83 |
84 | Hashtable freqUniqueWords = new Hashtable();
85 |
86 | // Set the frequency of all words to be 0.
87 | for (String unionVal : _union) {
88 | freqUniqueWords.put(unionVal, 0.0);
89 | }
90 |
91 | // Count how many times the word appears in the cleanedList, populate those
92 | // counts as values in the hash table.
93 | for (String word : cleanedList) {
94 | freqUniqueWords.put(word, freqUniqueWords.get(word) + 1);
95 | }
96 |
97 | return freqUniqueWords;
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/test/java/com/btieu/JobApplicationBot/TFIDFCalcTest.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import static org.junit.jupiter.api.Assertions.*;
4 |
5 | import java.io.IOException;
6 | import java.net.URISyntaxException;
7 | import java.util.Hashtable;
8 |
9 | import org.junit.jupiter.api.Test;
10 |
11 | /**
12 | * Test all methods in TFIDFCalc.
13 | *
14 | * @author bruce
15 | *
16 | */
17 | public class TFIDFCalcTest {
18 |
19 | private TFIDFCalcTestCases _wordListTestCases;
20 | private TextDocument _textDocument1, _textDocument2;
21 | private Hashtable _actualJobDescriptionTF, _actualResumeTF;
22 | private TFIDFCalc _tfidfCalc;
23 |
24 | /**
25 | * Constructor which initializes the necessary variables for testing.
26 | *
27 | * @throws IOException Catch any file reading errors.
28 | * @throws URISyntaxException
29 | */
30 | public TFIDFCalcTest() throws IOException, URISyntaxException {
31 | _wordListTestCases = new TFIDFCalcTestCases();
32 | _textDocument1 = new TextDocument(TFIDFCalcTestCases.RESUME_STRING);
33 | _textDocument2 = new TextDocument(TFIDFCalcTestCases.JOB_DESCRIPTION_STRING);
34 | _tfidfCalc = new TFIDFCalc(_textDocument1, _textDocument2);
35 | _actualJobDescriptionTF = _tfidfCalc.computeTF(_textDocument1.getWordsFromDocument());
36 | _actualResumeTF = _tfidfCalc.computeTF(_textDocument2.getWordsFromDocument());
37 | }
38 |
39 | @Test
40 | /**
41 | * Test the computeTF() method in TFIDFCalc.
42 | *
43 | * @throws IOException
44 | */
45 | void computeTFTest() throws IOException {
46 |
47 | assertEquals(TFIDFCalcTestCases.fakeJobDescriptionTF(), _actualJobDescriptionTF);
48 | assertEquals(TFIDFCalcTestCases.fakeResumeTF(), _actualResumeTF);
49 |
50 | }
51 |
52 | @Test
53 | /**
54 | * Test the computeIDF() method in TFIDFCalc.
55 | */
56 | void computeIDFTest() {
57 | assertEquals(TFIDFCalcTestCases.fakeIDF(), _tfidfCalc.computeIDF());
58 | }
59 |
60 | @Test
61 | /**
62 | * Test the computeTFIDF() method in TFIDFCalc.
63 | */
64 | void computeTFIDFTest() {
65 | assertEquals(_wordListTestCases.fakeJobDescriptionTFIDF(), _tfidfCalc.computeTFIDF(_actualJobDescriptionTF));
66 | assertEquals(_wordListTestCases.fakeResumeTFIDF(), _tfidfCalc.computeTFIDF(_actualResumeTF));
67 | }
68 |
69 | @Test
70 | /**
71 | * Test the runTFIDFTest() method in TFIDFCalc.
72 | *
73 | * @throws IOException
74 | */
75 | void runTFIDFTest() throws IOException {
76 | assertEquals(_wordListTestCases.fakeResumeTFIDF(), _tfidfCalc.runTFIDFCalc().getTFIDFHashDocB());
77 | assertEquals(_wordListTestCases.fakeJobDescriptionTFIDF(), _tfidfCalc.runTFIDFCalc().getTFIDFHashDocA());
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/test/java/com/btieu/JobApplicationBot/TestClass.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import static org.junit.jupiter.api.Assertions.assertEquals;
5 |
6 | class TestClass {
7 |
8 | @Test
9 | void addition() {
10 | assertEquals(1, 1);
11 | }
12 |
13 | }
--------------------------------------------------------------------------------
/JobApplicationBot/src/test/java/com/btieu/JobApplicationBot/WriteFilesTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Bruce Tieu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.btieu.JobApplicationBot;
18 |
19 | import java.io.IOException;
20 | import java.io.StringWriter;
21 | import java.io.Writer;
22 |
23 |
24 | import org.junit.jupiter.api.Test;
25 |
26 | import static org.junit.jupiter.api.Assertions.*;
27 |
28 | /*
29 | * This is the class to test the writing of beans to a CSV file.
30 | */
31 | class WriteFilesTest {
32 | private Writer _writer;
33 | private JobPostingData _jobBean;
34 |
35 | /*
36 | * This constructor initializes the writer, bean writer, and job bean.
37 | */
38 | public WriteFilesTest() {
39 | _writer = new StringWriter();
40 | _jobBean = new JobPostingData();
41 | }
42 |
43 | @Test
44 | /**
45 | * Test if writeJobPostToCSV() in WriteFiles works.
46 | * @throws IOException Throw an IOException if there's an error.
47 | */
48 | void testWriteJobPostToCSV() throws IOException {
49 |
50 | // Create a WriteFiles object, specify the filename to output the test results.
51 | WriteFiles writeFiles = new WriteFiles("testOutput.csv");
52 |
53 | // Store the string of what was written to the file.
54 | String actualOutput = writeFiles.writeJobPostToCSV(CSVTestCases.JOB_POSTING_BEAN);
55 |
56 | // Replace all double quotes with an empty string.
57 | actualOutput = actualOutput.replace("\"", "");
58 |
59 | // Check if the expected output is equal to what was actually written to the file.
60 | assertEquals(CSVTestCases.CSV_FILE, actualOutput);
61 | }
62 |
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/test/resources/com/btieu/JobApplicationBot/CSVTestCases.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Bruce Tieu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.btieu.JobApplicationBot;
17 |
18 | import java.util.Arrays;
19 | import java.util.List;
20 |
21 | import org.supercsv.cellprocessor.constraint.NotNull;
22 | import org.supercsv.cellprocessor.ift.CellProcessor;
23 |
24 | /**
25 | * This class builds the test data to test writeJobPostToCSV() method.
26 | *
27 | * @author bruce
28 | *
29 | */
30 | public class CSVTestCases {
31 | public static final String[] HEADER = new String[] { "jobMatchScore", "jobTitle", "companyName", "companyLoc", "remote",
32 | "dateApplied", "appType", "jobLink", "submitted", "jobStatus" };
33 | public static final String HEADER_CSV = "jobMatchScore,jobTitle,companyName,companyLoc,remote,dateApplied,appType,jobLink,submitted,jobStatus";
34 |
35 | public static final CellProcessor[] WRITE_PROCESSORS = new CellProcessor[] { new NotNull(), // jobMatchScore
36 | new NotNull(), // jobTitle
37 | new NotNull(), // companyName
38 | new NotNull(), // companyLocation
39 | new NotNull(), // remote
40 | new NotNull(), // dateApplied
41 | new NotNull(), // jobType
42 | new NotNull(), // jobLink
43 | new NotNull(), // submitted
44 | new NotNull() // jobStatus
45 | };
46 |
47 | // Create multiple test data. One is a string, the other is bean.
48 | public static final String JOB1_CSV = "0.34136279460203306,Full Stack Intern,Redko,Remote,yes,10/9/20 9:21,EASILY_APPLY,https://www.indeed.com/company/Redko/jobs/Full-Stack-Intern-737deae7d5296876?fccid=fef428aa4152bc0d&vjs=3,no,";
49 | public static final JobPostingData JOB1 = new JobPostingData(0.34136279460203306, "Full Stack Intern", "Redko",
50 | "Remote", "yes", "10/9/20 9:21", "EASILY_APPLY",
51 | "https://www.indeed.com/company/Redko/jobs/Full-Stack-Intern-737deae7d5296876?fccid=fef428aa4152bc0d&vjs=3",
52 | "no", "");
53 |
54 | public static final String JOB2_CSV = "0.3382327860757509,Full Stack Engineering Intern,U.S. Xpress Enterprises, Inc.,"
55 | + "Scottsdale, AZ"
56 | + ",yes,10/9/20 9:21,EASILY_APPLY,https://www.indeed.com/company/U.S.-Xpress/jobs/Full-Stack-Engineering-Intern-546d726323baa0a8?fccid=d921f5450b899369&vjs=3,no,";
57 | public static final JobPostingData JOB2 = new JobPostingData(0.3382327860757509, "Full Stack Engineering Intern",
58 | "U.S. Xpress Enterprises, Inc.", "Scottsdale, AZ", "yes", "10/9/20 9:21", "EASILY_APPLY",
59 | "https://www.indeed.com/company/U.S.-Xpress/jobs/Full-Stack-Engineering-Intern-546d726323baa0a8?fccid=d921f5450b899369&vjs=3",
60 | "no", "");
61 |
62 | public static final String JOB3_CSV = "0.2204924699317553,Intern Web Developer,Atlink Education,Remote,yes,10/9/20 9:22,EASILY_APPLY,https://www.indeed.com/company/Atlink-Education/jobs/Intern-Web-Developer-207f0c248bd8fb96?fccid=4837d661d2a76be2&vjs=3,no,";
63 | public static final JobPostingData JOB3 = new JobPostingData(0.2204924699317553, "Intern Web Developer",
64 | "Atlink Education", "Remote", "yes", "10/9/20 9:22", "EASILY_APPLY",
65 | "https://www.indeed.com/company/Atlink-Education/jobs/Intern-Web-Developer-207f0c248bd8fb96?fccid=4837d661d2a76be2&vjs=3",
66 | "no", "");
67 |
68 | public static final String JOB4_CSV = "0.30868099269670346,Software Engineer Intern,EcoCart,San Francisco, CA,yes,10/9/20 9:22,EASILY_APPLY,https://www.indeed.com/company/EcoCart/jobs/Software-Engineer-Intern-6c27e2ac38c37639?fccid=fadf1499cb147e15&vjs=3,no,";
69 | public static final JobPostingData JOB4 = new JobPostingData(0.30868099269670346, "Software Engineer Intern",
70 | "EcoCart", "San Francisco, CA", "yes", "10/9/20 9:22", "EASILY_APPLY",
71 | "https://www.indeed.com/company/EcoCart/jobs/Software-Engineer-Intern-6c27e2ac38c37639?fccid=fadf1499cb147e15&vjs=3",
72 | "no", "");
73 |
74 | public static final String JOB5_CSV = "0.31895032525956524,Software Engineer,Copilot,Denver, CO,yes,10/8/20 11:14,EASILY_APPLY,https://www.indeed.com/company/Copilot/jobs/Software-Engineer-f139d9dcdff1f88e?fccid=f4de6c53d8e539bd&vjs=3,no,";
75 | public static final JobPostingData JOB5 = new JobPostingData(0.31895032525956524, "Software Engineer", "Copilot",
76 | "Denver, CO", "yes", "10/8/20 11:14", "EASILY_APPLY",
77 | "https://www.indeed.com/company/Copilot/jobs/Software-Engineer-f139d9dcdff1f88e?fccid=f4de6c53d8e539bd&vjs=3",
78 | "no", "");
79 |
80 | // CSV_FILE is what we should expect the method to produce.
81 | public static final String CSV_FILE = new StringBuilder(HEADER_CSV).append("\r\n").append(JOB1_CSV).append("\r\n")
82 | .append(JOB2_CSV).append("\r\n").append(JOB3_CSV).append("\r\n").append(JOB4_CSV).append("\r\n")
83 | .append(JOB5_CSV).append("\r\n").toString();
84 |
85 | // JOB_POSTING_BEAN is passed into writeJobPostAsCSV(), which writes this list
86 | // into the specified file.
87 | public static final List JOB_POSTING_BEAN = Arrays.asList(JOB1, JOB2, JOB3, JOB4, JOB5);
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/JobApplicationBot/src/test/resources/com/btieu/JobApplicationBot/TFIDFCalcTestCases.java:
--------------------------------------------------------------------------------
1 | package com.btieu.JobApplicationBot;
2 |
3 | import java.util.Hashtable;
4 |
5 | /**
6 | * Class which generates and holds dummy data for tf-idf testing.
7 | *
8 | * @author bruce
9 | *
10 | */
11 | public class TFIDFCalcTestCases {
12 |
13 | // Generate fake text.
14 | public static final String JOB_DESCRIPTION_STRING = "A requirement for this job is JavaScript. Python is a wonderful language.";
15 | public static final String RESUME_STRING = "I am learning JavaScript. Java is a cool language.";
16 |
17 | public static Hashtable job_description_tf;
18 | public static Hashtable resume_tf;
19 | public static Hashtable idf;
20 |
21 | public Hashtable job_description_tfidf;
22 | public Hashtable resume_tfidf;
23 |
24 | /**
25 | * Initialize tf-idf hash tables.
26 | *
27 | * @param tfidfResume The tf-idf hash table for the resume.
28 | * @param tfidfJobDescription The tf-idf hash table for the job description.
29 | */
30 | public TFIDFCalcTestCases(Hashtable resume_tfidf, Hashtable job_description_tfidf) {
31 | this.resume_tfidf = resume_tfidf;
32 | this.job_description_tfidf = job_description_tfidf;
33 | }
34 |
35 | /**
36 | * Default constructor.
37 | */
38 | public TFIDFCalcTestCases() {
39 | }
40 |
41 | /**
42 | * Generate term frequency for words in job description.
43 | *
44 | * @return a hash table.
45 | */
46 | public static Hashtable fakeJobDescriptionTF() {
47 | job_description_tf = new Hashtable();
48 | job_description_tf.put("am", (double) 1 / 7);
49 | job_description_tf.put("learning", (double) 1 / 7);
50 | job_description_tf.put("javascript", (double) 1 / 7);
51 | job_description_tf.put("java", (double) 1 / 7);
52 | job_description_tf.put("is", (double) 1 / 7);
53 | job_description_tf.put("cool", (double) 1 / 7);
54 | job_description_tf.put("language", (double) 1 / 7);
55 | job_description_tf.put("requirement", (double) 0);
56 | job_description_tf.put("for", (double) 0);
57 | job_description_tf.put("this", (double) 0);
58 | job_description_tf.put("job", (double) 0);
59 | job_description_tf.put("python", (double) 0);
60 | job_description_tf.put("wonderful", (double) 0);
61 | return job_description_tf;
62 | }
63 |
64 | /**
65 | * Generate term frequency for words in resume.
66 | *
67 | * @return a hash table.
68 | */
69 | public static Hashtable fakeResumeTF() {
70 | resume_tf = new Hashtable();
71 | resume_tf.put("am", (double) 0);
72 | resume_tf.put("learning", (double) 0);
73 | resume_tf.put("javascript", (double) 1 / 10);
74 | resume_tf.put("java", (double) 0);
75 | resume_tf.put("is", (double) 2 / 10);
76 | resume_tf.put("cool", (double) 0);
77 | resume_tf.put("language", (double) 1 / 10);
78 | resume_tf.put("requirement", (double) 1 / 10);
79 | resume_tf.put("for", (double) 1 / 10);
80 | resume_tf.put("this", (double) 1 / 10);
81 | resume_tf.put("job", (double) 1 / 10);
82 | resume_tf.put("python", (double) 1 / 10);
83 | resume_tf.put("wonderful", (double) 1 / 10);
84 | return resume_tf;
85 | }
86 |
87 | /**
88 | * Generate IDF.
89 | *
90 | * @return a hash table.
91 | */
92 | public static Hashtable fakeIDF() {
93 | idf = new Hashtable();
94 | idf.put("am", (double) Math.log(2));
95 | idf.put("learning", (double) Math.log(2));
96 | idf.put("javascript", (double) 0);
97 | idf.put("java", (double) Math.log(2));
98 | idf.put("is", (double) 0);
99 | idf.put("cool", (double) Math.log(2));
100 | idf.put("language", (double) 0);
101 | idf.put("requirement", (double) Math.log(2));
102 | idf.put("for", (double) Math.log(2));
103 | idf.put("this", (double) Math.log(2));
104 | idf.put("job", (double) Math.log(2));
105 | idf.put("python", (double) Math.log(2));
106 | idf.put("wonderful", (double) Math.log(2));
107 | return idf;
108 | }
109 |
110 | /**
111 | * Generate the tf-idf table for the fake job description text.
112 | *
113 | * @return A hash table.
114 | */
115 | public Hashtable fakeJobDescriptionTFIDF() {
116 | job_description_tfidf = new Hashtable();
117 | job_description_tfidf.put("am", (double) 1 / 7 + (double) 1 / 7 * Math.log(2));
118 | job_description_tfidf.put("learning", (double) 1 / 7 + (double) 1 / 7 * Math.log(2));
119 | job_description_tfidf.put("javascript", (double) 1 / 7);
120 | job_description_tfidf.put("java", (double) 1 / 7 + (double) 1 / 7 * Math.log(2));
121 | job_description_tfidf.put("is", (double) 1 / 7);
122 | job_description_tfidf.put("cool", (double) 1 / 7 + (double) 1 / 7 * Math.log(2));
123 | job_description_tfidf.put("language", (double) 1 / 7);
124 | job_description_tfidf.put("requirement", (double) 0);
125 | job_description_tfidf.put("for", (double) 0);
126 | job_description_tfidf.put("this", (double) 0);
127 | job_description_tfidf.put("job", (double) 0);
128 | job_description_tfidf.put("python", (double) 0);
129 | job_description_tfidf.put("wonderful", (double) 0);
130 | return job_description_tfidf;
131 | }
132 |
133 | /**
134 | * Generate the tf-idf table for the fake resume text.
135 | *
136 | * @return a hash table.
137 | */
138 | public Hashtable fakeResumeTFIDF() {
139 | resume_tfidf = new Hashtable();
140 | resume_tfidf.put("am", (double) 0);
141 | resume_tfidf.put("learning", (double) 0);
142 | resume_tfidf.put("javascript", (double) 1 / 10);
143 | resume_tfidf.put("java", (double) 0);
144 | resume_tfidf.put("is", (double) 2 / 10);
145 | resume_tfidf.put("cool", (double) 0);
146 | resume_tfidf.put("language", (double) 1 / 10);
147 | resume_tfidf.put("requirement", (double) 1 / 10 + (double) 1 / 10 * Math.log(2));
148 | resume_tfidf.put("for", (double) 1 / 10 + (double) 1 / 10 * Math.log(2));
149 | resume_tfidf.put("this", (double) 1 / 10 + (double) 1 / 10 * Math.log(2));
150 | resume_tfidf.put("job", (double) 1 / 10 + (double) 1 / 10 * Math.log(2));
151 | resume_tfidf.put("python", (double) 1 / 10 + (double) 1 / 10 * Math.log(2));
152 | resume_tfidf.put("wonderful", (double) 1 / 10 + (double) 1 / 10 * Math.log(2));
153 | return resume_tfidf;
154 | }
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SUDO=$(shell which sudo)
2 |
3 | default:
4 | echo "Nothing to do"
5 |
6 | install:
7 | $(SUDO) apt update
8 | $(SUDO) apt install -y openjdk-8-jdk-headless maven
9 |
10 | test:
11 | @# Run tests.
12 | @mvn test
13 |
14 | @make clean > /dev/null
15 |
16 | build:
17 | @# Build executable jar file.
18 | @mvn clean compile assembly:single
19 |
20 | documentation:
21 | @# Generate documentation.
22 | @mvn -Dmaven.javadoc.skip=true verify clean site
23 |
24 | clean:
25 | @rm -rf target
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Job Application Bot
2 | This bot saves jobs by different types on Indeed and Glassdoor and exports them to a csv file. It can also apply to job forms which are easily filled out, such as Lever and Greenhouse forms. Finally, there is a LinkedIn connections bot which automatically sends connection requests to people based on keywords supplied.
3 |
4 |
5 | Download the jar file here: btieu.gitlab.io/job-application-bot/
6 |
7 | 
8 |
9 |
10 | ## Installation
11 |
12 | ```
13 | make install
14 | ```
15 |
16 | ## Running the tests
17 |
18 | ```
19 | make test
20 | ```
21 |
22 | ## Building the docs
23 |
24 | ```
25 | make documentation
26 | ```
27 |
28 | ## Building the executable
29 | ```
30 | make build
31 | ```
32 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Job Application Bot Deployment.
7 |
8 |
9 | Hello, Deployment.
10 | Download the job bot here
11 | View the documentation here
12 |
13 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 4.0.0
7 | com.btieu.JobApplicationBot
8 | JobApplicationBot
9 | jar
10 | 1.0-SNAPSHOT
11 |
12 |
13 |
14 | UTF-8
15 | 1.8
16 | 1.8
17 |
18 |
19 |
20 |
21 |
22 |
23 | org.junit.jupiter
24 | junit-jupiter-engine
25 | 5.3.1
26 | test
27 |
28 |
29 |
30 |
31 | org.seleniumhq.selenium
32 | selenium-java
33 | 3.141.59
34 |
35 |
36 |
37 |
38 | org.swinglabs
39 | swingx
40 | 1.6.1
41 |
42 |
43 |
44 |
45 | net.java.abeille
46 | abeille
47 | 3.0
48 |
49 |
50 |
51 |
52 | org.apache.pdfbox
53 | pdfbox
54 | 2.0.24
55 |
56 |
57 |
58 |
59 | net.sf.supercsv
60 | super-csv
61 | 2.4.0
62 |
63 |
64 |
65 |
66 |
67 | BotGUI
68 | JobApplicationBot/src/main
69 | JobApplicationBot/src
70 |
71 |
72 |
73 | org.apache.maven.plugins
74 | maven-surefire-plugin
75 | 3.0.0-M5
76 |
77 |
78 |
79 | org.apache.maven.plugins
80 | maven-site-plugin
81 | 3.9.1
82 |
83 |
84 |
85 |
86 | maven-assembly-plugin
87 |
88 |
89 |
90 | true
91 | net.codejava.swing.BotGUI
92 |
93 |
94 |
95 | jar-with-dependencies
96 |
97 | false
98 |
99 |
100 |
101 | make-assembly
102 | package
103 |
104 | single
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | org.apache.maven.plugins
118 | maven-javadoc-plugin
119 | 3.2.0
120 |
121 | /usr/bin/javadoc
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------