View Javadoc

1   /*------------------------------------------------------------------------------
2    * The contents of this file are subject to the Mozilla Public License Version
3    * 1.1 (the "License"); you may not use this file except in compliance with
4    * the License. You may obtain a copy of the License at
5    * http://www.mozilla.org/MPL/
6    * Software distributed under the License is distributed on an "AS IS" basis,
7    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
8    * the specific language governing rights and limitations under the License.
9    *
10   * The Original Code is levelonelabs.com code.
11   * The Initial Developer of the Original Code is Level One Labs. Portions
12   * created by the Initial Developer are Copyright (C) 2001 the Initial
13   * Developer. All Rights Reserved.
14   *
15   *         Contributor(s):
16   *             Scott Oster      (ostersc@alum.rpi.edu)
17   *             Steve Zingelwicz (sez@po.cwru.edu)
18   *             William Gorman   (willgorman@hotmail.com)
19   *
20   * Alternatively, the contents of this file may be used under the terms of
21   * either the GNU General Public License Version 2 or later (the "GPL"), or
22   * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
23   * in which case the provisions of the GPL or the LGPL are applicable
24   * instead of those above. If you wish to allow use of your version of this
25   * file only under the terms of either the GPL or the LGPL, and not to allow
26   * others to use your version of this file under the terms of the NPL, indicate
27   * your decision by deleting the provisions above and replace them with the
28   * notice and other provisions required by the GPL or the LGPL. If you do not
29   * delete the provisions above, a recipient may use your version of this file
30   * under the terms of any one of the NPL, the GPL or the LGPL.
31   *----------------------------------------------------------------------------*/
32  
33  package com.levelonelabs.aimbot;
34  
35  import java.io.File;
36  import java.io.IOException;
37  import java.io.InputStream;
38  import java.util.ArrayList;
39  import java.util.Enumeration;
40  import java.util.Hashtable;
41  import java.util.Iterator;
42  import java.util.List;
43  import java.util.Properties;
44  import java.util.StringTokenizer;
45  import java.util.logging.FileHandler;
46  import java.util.logging.Handler;
47  import java.util.logging.Level;
48  import java.util.logging.Logger;
49  
50  import javax.xml.parsers.DocumentBuilder;
51  import javax.xml.parsers.DocumentBuilderFactory;
52  import javax.xml.parsers.ParserConfigurationException;
53  import javax.xml.transform.Result;
54  import javax.xml.transform.Source;
55  import javax.xml.transform.Transformer;
56  import javax.xml.transform.TransformerFactory;
57  import javax.xml.transform.dom.DOMSource;
58  import javax.xml.transform.stream.StreamResult;
59  
60  import org.w3c.dom.Document;
61  import org.w3c.dom.Element;
62  import org.w3c.dom.NodeList;
63  
64  import com.levelonelabs.aim.AIMAdapter;
65  import com.levelonelabs.aim.AIMBuddy;
66  import com.levelonelabs.aim.AIMClient;
67  import com.levelonelabs.aim.AIMGroup;
68  import com.levelonelabs.aim.AIMSender;
69  import com.levelonelabs.aim.XMLizable;
70  
71  
72  /***
73   * The main Aimbot that uses the aim package to communicate with AIM and
74   * provides services via AIM through various bot modules that perform action
75   * when clients make specific queries to the screename registered by this bot.
76   * 
77   * @author Scott Oster (ostersc@alumn.rpi.edu)
78   * 
79   * @created January 10, 2002
80   */
81  public class AIMBot extends AIMAdapter {
82  	public static final String ROLE_USER = "User";
83  	public static final String ROLE_ENEMY = "Enemy";
84  	public static final String ROLE_ADMINISTRATOR = "Administrator";
85  
86  	private static final String PERSISTANCE_FILENAME = "persistance.xml";
87  	private static Logger logger = Logger.getLogger(AIMBot.class.getName());
88  	/*** A handle to the outgoing AIM connection */
89  	protected AIMSender aim;
90  	private String username;
91  	private String password;
92  	private boolean autoAdd = false;
93  	private Hashtable services;
94  	private Hashtable groups;
95  	private List modules;
96  	private boolean enforceUser;
97  	private String nonUserResponse;
98  
99  
100 	/***
101 	 * Constructor for the AIMBot object
102 	 */
103 	public AIMBot() {
104 		services = new Hashtable();
105 		groups = new Hashtable();
106 		modules = new ArrayList();
107 	}
108 
109 
110 	/***
111 	 * Add the specified username as an admin of the bot, if it is not already
112 	 * 
113 	 * @param admin
114 	 */
115 	private void verifyAdmin(String admin) {
116 		if (admin.equals("")) {
117 			return;
118 		}
119 		AIMBuddy adminBuddy = aim.getBuddy(admin);
120 		if (adminBuddy == null) {
121 			adminBuddy = new AIMBuddy(admin);
122 		}
123 		if (!adminBuddy.hasRole(AIMBot.ROLE_USER)) {
124 			adminBuddy.addRole(AIMBot.ROLE_USER);
125 			logger.info("Adding " + AIMBot.ROLE_USER + " role to " + admin);
126 		}
127 		if (!adminBuddy.hasRole(AIMBot.ROLE_ADMINISTRATOR)) {
128 			adminBuddy.addRole(AIMBot.ROLE_ADMINISTRATOR);
129 			logger.info("Adding " + AIMBot.ROLE_ADMINISTRATOR + " role to " + admin);
130 		}
131 		if (aim.getBuddy(adminBuddy.getName()) == null) {
132 			aim.addBuddy(adminBuddy);
133 			logger.info("Adding " + admin + " as an admin.");
134 		}
135 	}
136 
137 
138 	/***
139 	 * Start up the bot.
140 	 * 
141 	 * @param args
142 	 *            The command line arguments
143 	 */
144 	public static void main(String[] args) {
145 		String propsFile = System.getProperty("config.file", "bot.properties");
146 
147 		AIMBot bot = new AIMBot();
148 		bot.init(propsFile);
149 	}
150 
151 
152 	/***
153 	 * Read in the properties file and configure the bot from it.
154 	 * 
155 	 * @param propertiesFileName
156 	 *            the name of the properties file to look for with the
157 	 *            classloader.
158 	 */
159 	public void init(String propertiesFileName) {
160 		/*
161 		 * We use a properties file to define the username,password, and
162 		 * optionally the log level
163 		 */
164 		Properties props = null;
165 		try {
166 			InputStream is = ClassLoader.getSystemResourceAsStream(propertiesFileName);
167 			props = new Properties();
168 			props.load(is);
169 		} catch (Exception e) {
170 			logger.severe("Failed to load props file (" + propertiesFileName + ")!");
171 			logger
172 				.severe("You must configure bot.properties, or add -Dconfig.file=<CONFIGFILE> to use a different one(must be on classpath).");
173 			e.printStackTrace();
174 			System.exit(-1);
175 		}
176 
177 		//Setup the aim options
178 		String userP = props.getProperty("aim.username");
179 
180 		//remove whitespace from username, as AOL ignores it
181 		userP = userP.replaceAll(" ", "");
182 		String passP = props.getProperty("aim.password");
183 		String autoaddP = props.getProperty("bot.autoadd", "false").trim();
184 		String enforceUserP = props.getProperty("bot.enforceUser", "true").trim();
185 		String profileP = props.getProperty("bot.profile", "I'm running code from:\nhttp://jaimbot.sourceforge.net/")
186 			.trim();
187 		String nonUserResponseP = props.getProperty("bot.nonuser.response",
188 			"Sorry, you must be a user of this system to send requests.").trim();
189 
190 		setupAIM(userP, passP, profileP, nonUserResponseP, autoaddP, enforceUserP);
191 
192 		//Setup the Logger
193 		String loglevel = props.getProperty("logger.level");
194 		String logFile = props.getProperty("logger.file", username + "_aimbot.log.xml");
195 		setupLogger(loglevel, logFile);
196 
197 		String autoPersist = props.getProperty("bot.autopersist", "true").trim();
198 		//sets autoPersist
199 		if (autoPersist.equalsIgnoreCase("true")) {
200 			Runtime.getRuntime().addShutdownHook(new Thread() {
201 				public void run() {
202 					persist();
203 				}
204 			});
205 		}
206 
207 		//load in the mod names
208 		List modNames = new ArrayList();
209 		for (int i = 0; i < props.size(); i++) {
210 			if (props.containsKey("mod." + i)) {
211 				String modName = props.getProperty("mod." + i);
212 				modNames.add(modName);
213 			}
214 		}
215 		loadModules(modNames);
216 
217 		depersist();
218 		String admin = props.getProperty("bot.admin", "").trim();
219 
220 		//remove whitespace from username, as AOL ignores it
221 		admin = admin.replaceAll(" ", "");
222 		verifyAdmin(admin);
223 		aim.signOn();
224 	}
225 
226 
227 	private void setupAIM(String user, String pass, String profile, String nonUserResponse, String autoAddUsers,
228 		String enforceUser) {
229 		this.nonUserResponse = nonUserResponse;
230 		if ((user == null) || (pass == null) || user.trim().equals("") || pass.trim().equals("")) {
231 			logger.severe("ERROR: invalid username or password.");
232 			System.exit(-1);
233 		} else {
234 			this.username = user;
235 			this.password = pass;
236 		}
237 
238 		//check for autoadd
239 		this.autoAdd = false;
240 		if (autoAddUsers.equalsIgnoreCase("true")) {
241 			this.autoAdd = true;
242 		}
243 
244 		//check for enforceUser
245 		this.enforceUser = true;
246 		if (enforceUser.equalsIgnoreCase("false")) {
247 			this.enforceUser = false;
248 		}
249 
250 		aim = new AIMClient(username, password, profile, nonUserResponse, this.autoAdd);
251 		aim.addAIMListener(this);
252 	}
253 
254 
255 	private void setupLogger(String logLevel, String logFilename) {
256 		Level level = null;
257 		try {
258 			level = Level.parse(logLevel);
259 		} catch (Exception e) {
260 			System.err.println("ERROR parsing log level, defaulting to INFO");
261 			level = Level.INFO;
262 		}
263 
264 		try {
265 			Handler fh = new FileHandler(logFilename);
266 			Logger.getLogger("").addHandler(fh);
267 
268 			Logger.getLogger("").setLevel(level);
269 			Handler[] handlers = Logger.getLogger("").getHandlers();
270 			for (int i = 0; i < handlers.length; i++) {
271 				handlers[i].setLevel(level);
272 			}
273 		} catch (Exception e) {
274 			logger.severe("ERROR: unable to attach FileHandler to logger!");
275 			e.printStackTrace();
276 		}
277 	}
278 
279 
280 	/***
281 	 * Returns the AIM username of the bot.
282 	 * 
283 	 * @return the AIM username of the bot.
284 	 */
285 	public String getUsername() {
286 		return username;
287 	}
288 
289 
290 	/***
291 	 * Gets the specified group
292 	 * 
293 	 * @param groupName
294 	 * 
295 	 * @return The group value
296 	 */
297 	public AIMGroup getGroup(String groupName) {
298 		return (AIMGroup) this.groups.get(groupName);
299 	}
300 
301 
302 	/***
303 	 * Gets an enum of the groups of the AIMBot
304 	 * 
305 	 * @return The groups enumeration
306 	 */
307 	public Enumeration getGroups() {
308 		return this.groups.elements();
309 	}
310 
311 
312 	/***
313 	 * Will call the appropriate bot module to service the request.
314 	 * 
315 	 * @param buddy
316 	 *            the buddy making the request
317 	 * @param request
318 	 *            the text of the request
319 	 */
320 	public void handleMessage(AIMBuddy buddy, String request) {
321 		if (buddy != null) {
322 			logger.info(buddy.getName() + " said: " + request);
323 		} else {
324 			logger.info("Ignoring request:" + request + " from null buddy.");
325 			return;
326 		}
327 
328 		if (buddy.hasRole(ROLE_ENEMY)) {
329 			logger.info("Ignoring request:" + request + " from Enemy:" + buddy.getName() + " and attempting to warn");
330 			retaliate(buddy);
331 			return;
332 		}
333 
334 		if (this.enforceUser && !buddy.hasRole(ROLE_USER)) {
335 			//if we are enforcing the user list and the buddy isnt a user,
336 			// ignore the request and send the non-user response
337 			logger.info("Ignoring request:" + request + " from buddy that isn't a user, sending non user response.");
338 			if (this.nonUserResponse != null && !this.nonUserResponse.trim().equals("")) {
339 				aim.sendMessage(buddy, this.nonUserResponse);
340 			}
341 			return;
342 		}
343 
344 		StringTokenizer stok = new StringTokenizer(request, " ");
345 		String keyword = "";
346 		if (stok.hasMoreTokens()) {
347 			keyword = stok.nextToken().toLowerCase().trim();
348 		}
349 
350 		//handle bot functionality first
351 		if (keyword.equals("help")) {
352 			StringBuffer sb = new StringBuffer();
353 			if (stok.hasMoreTokens()) {
354 				try {
355 					int ind = Integer.parseInt(stok.nextToken());
356 					BotModule bm = (BotModule) modules.get(ind);
357 					sb.append("Help for " + bm.getName() + ":\n" + bm.help());
358 					aim.sendMessage(buddy, sb.toString());
359 					return;
360 				} catch (Exception e) {
361 				}
362 			}
363 			sb.append("For information on a specific service, type <I>help</I>");
364 			sb.append(" followed by the service number listed below.\n");
365 			sb.append("<B>Current Services:</B>\n");
366 			for (int i = 0; i < modules.size(); i++) {
367 				sb.append(i + ") " + ((BotModule) modules.get(i)).getName() + "    ");
368 			}
369 			sb.append("\n<b>EXAMPLE: help 1</b>");
370 
371 			aim.sendMessage(buddy, sb.toString());
372 			return;
373 		} else if (keyword.equals("persist")) {
374 			if (buddy.hasRole(AIMBot.ROLE_ADMINISTRATOR)) {
375 				boolean success = this.persist();
376 				if (success) {
377 					aim.sendMessage(buddy, "Persistance Succeeded.");
378 				} else {
379 					aim.sendMessage(buddy, "Persistance Failed!");
380 				}
381 			}
382 
383 			return;
384 		}
385 
386 		BotModule mod = (BotModule) services.get(keyword);
387 		if (mod != null) {
388 			logger.info("Request(" + keyword + ") being serviced by: " + mod.getName());
389 			mod.performService(buddy, request);
390 		} else {
391 			mod = (BotModule) modules.get(0);
392 			if (mod != null) {
393 				logger.info("Default Request (" + keyword + ") being serviced by: " + mod.getName());
394 				mod.performService(buddy, request);
395 			} else {
396 				logger.severe("Couldn't find mod to service request(" + keyword + ").");
397 			}
398 		}
399 	}
400 
401 
402 	/***
403 	 * Called when AIM encounters an error
404 	 * 
405 	 * @param error
406 	 *            the error code
407 	 * @param message
408 	 *            decsriptive text describing the error
409 	 */
410 	public void handleError(String error, String message) {
411 		logger.severe("ERROR(" + error + "): " + message);
412 	}
413 
414 
415 	/***
416 	 * Handle being warned from others. Mark them with the Enemy role and warn
417 	 * them back after a witty response.
418 	 * 
419 	 * @param enemy
420 	 *            the buddy that warned us
421 	 * @param amount
422 	 *            the current warning level
423 	 */
424 	public void handleWarning(AIMBuddy enemy, int amount) {
425 		if ((enemy == null) || (enemy.getName().equals("anonymous"))) {
426 			logger.info("AIMBot UNDER ATTACK!: anonymous raised warning to " + amount + " ***");
427 			return;
428 		}
429 		logger.info("AIMBot UNDER ATTACK!: " + enemy.getName() + " raised warning to " + amount + " ***");
430 
431 		retaliate(enemy);
432 	}
433 
434 
435 	/***
436 	 * Warn a user, and mark them as an Enemy. If they were already an Enemy,
437 	 * ban them.
438 	 * 
439 	 * @param enemy
440 	 */
441 	private void retaliate(AIMBuddy enemy) {
442 		if (!enemy.hasRole(ROLE_ENEMY)) {
443 			enemy.addRole(ROLE_ENEMY);
444 			aim.sendMessage(enemy,
445 				"In the End, we will remember not the words of our enemies, but the silence of our friends");
446 		} else {
447 			aim.banBuddy(enemy);
448 		}
449 		aim.sendWarning(enemy);
450 	}
451 
452 
453 	/***
454 	 * Adds a Group
455 	 * 
456 	 * @param group
457 	 *            The feature to be added to the Group attribute
458 	 */
459 	public void addGroup(AIMGroup group) {
460 		this.groups.put(group.getName(), group);
461 	}
462 
463 
464 	/***
465 	 * Removes the specified group
466 	 * 
467 	 * @param group
468 	 */
469 	public void removeGroup(AIMGroup group) {
470 		this.groups.remove(group.getName());
471 	}
472 
473 
474 	/***
475 	 * All bot modules will call this and pass a reference to themselves and an
476 	 * ArrayList containing the keywords they want to listen for
477 	 * 
478 	 * @param mod
479 	 *            the module
480 	 */
481 	protected void registerModule(BotModule mod) {
482 		this.modules.add(mod);
483 		ArrayList servicesList = mod.getServices();
484 		if (servicesList != null) {
485 			for (int i = 0; i < servicesList.size(); i++) {
486 				registerService((String) servicesList.get(i), mod);
487 			}
488 		}
489 	}
490 
491 
492 	/***
493 	 * Load BotModules and register them
494 	 * 
495 	 * @param modNames
496 	 *            List of names to load
497 	 */
498 	private void loadModules(List modNames) {
499 		//wipe the slate clean
500 		services = new Hashtable();
501 		modules = new ArrayList();
502 
503 		//iterate the modules
504 		for (int i = 0; i < modNames.size(); i++) {
505 			try {
506 				//grab a mod class
507 				String modName = (String) modNames.get(i);
508 				Class modclass = Class.forName(modName);
509 				java.lang.reflect.Constructor constructor;
510 				Class[] carr = {this.getClass()};
511 				Object[] oarr = {this};
512 
513 				//make a constructor
514 				constructor = modclass.getConstructor(carr);
515 				//get an instance
516 				BotModule mod = (BotModule) constructor.newInstance(oarr);
517 
518 				//register the mod
519 				registerModule(mod);
520 				logger.info("Loading mod (" + mod.getName() + ")");
521 			} catch (Exception e) {
522 				e.printStackTrace();
523 				logger.severe("Unable to load mod=" + modNames.get(i));
524 			}
525 		}
526 	}
527 
528 
529 	/***
530 	 * Method to persist all state to XML. Saves username, password, all
531 	 * buddies, all groups. Modules and services will be reloaded on
532 	 * depersistance so we dont need to save them; just give them a chance to
533 	 * persist and then depersist them next time loadmoduels is called.
534 	 * 
535 	 * @return returns true iff the persistance succeeded.
536 	 */
537 	private boolean persist() {
538 		Document doc = createDomDocument();
539 		Element root = doc.createElement("AIMBot_Persistance");
540 		root.setAttribute("username", this.username);
541 		//root.setAttribute("password", this.password);
542 		doc.appendChild(root);
543 
544 		//add buddies
545 		Element buddiesElem = doc.createElement("buddies");
546 		for (Iterator iter = aim.getBuddyNames(); iter.hasNext();) {
547 			String name = (String) iter.next();
548 			AIMBuddy bud = aim.getBuddy(name);
549 			if (bud == null) {
550 				continue;
551 			}
552 			Element buddyElem = doc.createElement("buddy");
553 			bud.writeState(buddyElem);
554 			buddiesElem.appendChild(buddyElem);
555 		}
556 		root.appendChild(buddiesElem);
557 
558 		//add groups
559 		Element groupsElem = doc.createElement("groups");
560 		for (Iterator iter = groups.keySet().iterator(); iter.hasNext();) {
561 			String name = (String) iter.next();
562 			AIMGroup grp = (AIMGroup) groups.get(name);
563 			if (grp == null) {
564 				continue;
565 			}
566 			Element groupElem = doc.createElement("group");
567 			grp.writeState(groupElem);
568 			groupsElem.appendChild(groupElem);
569 		}
570 		root.appendChild(groupsElem);
571 
572 		//add modules
573 		Element modsElem = doc.createElement("modules");
574 		for (int i = 0; i < modules.size(); i++) {
575 			BotModule mod = (BotModule) modules.get(i);
576 			if (mod instanceof XMLizable) {
577 				//persist the mod
578 				Element modElem = doc.createElement("module");
579 				modElem.setAttribute("name", mod.getName());
580 				try {
581 					((XMLizable) mod).writeState(modElem);
582 					modsElem.appendChild(modElem);
583 				} catch (Throwable t) {
584 					logger.severe("Problem persisting mod +[" + mod.getName() + "], ignoring it.");
585 				}
586 			}
587 		}
588 		root.appendChild(modsElem);
589 
590 		try {
591 			// Prepare the DOM document for writing
592 			Source source = new DOMSource(doc);
593 
594 			// Prepare the output file
595 			File file = new File(this.username + "_" + PERSISTANCE_FILENAME);
596 			Result result = new StreamResult(file);
597 
598 			// Write the DOM document to the file
599 			Transformer xformer = TransformerFactory.newInstance().newTransformer();
600 			xformer.transform(source, result);
601 
602 		} catch (Exception e) {
603 			logger.severe("ERROR: failed to persist!");
604 			e.printStackTrace();
605 			return false;
606 		}
607 
608 		logger.info("Successfully persisted AIMBot to file:" + this.username + "_" + PERSISTANCE_FILENAME);
609 
610 		return true;
611 	}
612 
613 
614 	/***
615 	 * Method depersist.
616 	 * 
617 	 * @return returns true iff the depersistance succeeded.
618 	 */
619 	private boolean depersist() {
620 		// Create a DOM builder
621 		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
622 
623 		// Parse the XML String to create a document
624 		Document d;
625 		try {
626 			d = factory.newDocumentBuilder().parse(new File(this.username + "_" + PERSISTANCE_FILENAME));
627 		} catch (IOException e) {
628 			logger.info("Couldn't locate persistance file; starting fresh.");
629 			return false;
630 		} catch (Exception e) {
631 			logger.severe("Error reading persistance file; aborting depersitance.");
632 			e.printStackTrace();
633 			return false;
634 		}
635 
636 		// Get document root
637 		Element root = d.getDocumentElement();
638 
639 		if ((root == null) || !root.getTagName().equalsIgnoreCase("AIMBot_Persistance")) {
640 			logger.severe("Error parsing persistance file; aborting depersitance.");
641 			return false;
642 		}
643 
644 		//parse buddies
645 		Element buddiesTag = (Element) root.getElementsByTagName("buddies").item(0);
646 		NodeList list = buddiesTag.getElementsByTagName("buddy");
647 		for (int i = 0; i < list.getLength(); i++) {
648 			Element buddyElem = (Element) list.item(i);
649 			String name = buddyElem.getAttribute("name");
650 
651 			AIMBuddy buddy = new AIMBuddy(name);
652 			buddy.readState(buddyElem);
653 			aim.addBuddy(buddy);
654 		}
655 
656 		//parse groups
657 		Element groupsTag = (Element) root.getElementsByTagName("groups").item(0);
658 		list = groupsTag.getElementsByTagName("group");
659 		for (int i = 0; i < list.getLength(); i++) {
660 			Element groupElem = (Element) list.item(i);
661 			String name = groupElem.getAttribute("name");
662 
663 			AIMGroup group = new AIMGroup(name);
664 			group.readState(groupElem);
665 			addGroup(group);
666 		}
667 
668 		//parse modules
669 		Element modsTag = (Element) root.getElementsByTagName("modules").item(0);
670 		list = modsTag.getElementsByTagName("module");
671 		for (int i = 0; i < list.getLength(); i++) {
672 			Element modElem = (Element) list.item(i);
673 			String name = modElem.getAttribute("name");
674 			for (int j = 0; j < modules.size(); j++) {
675 				BotModule mod = (BotModule) modules.get(j);
676 				if ((mod.getName().equals(name)) && (mod instanceof XMLizable)) {
677 					((XMLizable) mod).readState(modElem);
678 				}
679 			}
680 		}
681 
682 		logger.info("Successfully depersisted AIMBot from file:" + this.username + "_" + PERSISTANCE_FILENAME);
683 
684 		return true;
685 	}
686 
687 
688 	/***
689 	 * createDomDocument - creates an empty document
690 	 * 
691 	 * @return Document - returns an empty document
692 	 */
693 	protected static Document createDomDocument() {
694 		try {
695 			DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
696 			Document doc = builder.newDocument();
697 			return doc;
698 		} catch (ParserConfigurationException e) {
699 			e.printStackTrace();
700 		}
701 
702 		return null;
703 	}
704 
705 
706 	/***
707 	 * Registers a specific module to handle requests that start with a specific
708 	 * string
709 	 * 
710 	 * @param keyword
711 	 *            a word to look for in client text that identifies a bot module
712 	 *            to be called
713 	 * @param mod
714 	 *            the bot module that will handle this request
715 	 */
716 	private void registerService(String keyword, BotModule mod) {
717 		services.put(keyword, mod);
718 	}
719 }