├── .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 | ![Screen Shot 2020-12-29 at 11 42 41 AM](https://user-images.githubusercontent.com/56370411/103306577-1d147200-49cb-11eb-8e7b-450de195662d.png) 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 | --------------------------------------------------------------------------------