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.modules;
34  
35  import java.io.BufferedReader;
36  import java.io.InputStreamReader;
37  
38  import java.net.HttpURLConnection;
39  import java.net.URL;
40  import java.net.URLConnection;
41  
42  import java.util.ArrayList;
43  import java.util.HashMap;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.StringTokenizer;
48  import java.util.logging.Level;
49  import java.util.logging.Logger;
50  
51  import javax.swing.text.MutableAttributeSet;
52  import javax.swing.text.html.HTML;
53  import javax.swing.text.html.HTML.Tag;
54  import javax.swing.text.html.HTMLEditorKit.ParserCallback;
55  import javax.swing.text.html.parser.ParserDelegator;
56  
57  import com.levelonelabs.aim.AIMBuddy;
58  import com.levelonelabs.aimbot.AIMBot;
59  import com.levelonelabs.aimbot.BotModule;
60  
61  
62  /***
63   * Handles requests to get tv episode summaries for a particular show. This is
64   * extremely brittle, and should be considered expiremental.
65   * 
66   * @author Scott Oster
67   * 
68   * @todo: find the "best" matching show and use that instead of just the first
69   *        one.
70   * 
71   * @created March 25, 2003
72   */
73  public class TVListingsModule extends BotModule {
74  	static Logger logger = Logger.getLogger(TVListingsModule.class.getName());
75  	private static ArrayList services;
76  
77  	/***
78  	 * Initialize the service commands.
79  	 */
80  	static {
81  		services = new ArrayList();
82  		services.add("tv");
83  		services.add("tvlist");
84  	}
85  
86  
87  	/***
88  	 * Constructor for TVListingsModule.
89  	 * 
90  	 * @param bot
91  	 */
92  	public TVListingsModule(AIMBot bot) {
93  		super(bot);
94  	}
95  
96  
97  	/***
98  	 * @see com.levelonelabs.aimbot.BotModule#getName()
99  	 */
100 	public String getName() {
101 		return "TV Listings Module";
102 	}
103 
104 
105 	/***
106 	 * @see com.levelonelabs.aimbot.BotModule#getServices()
107 	 */
108 	public ArrayList getServices() {
109 		return services;
110 	}
111 
112 
113 	/***
114 	 * @see com.levelonelabs.aimbot.BotModule#help()
115 	 */
116 	public String help() {
117 		StringBuffer sb = new StringBuffer();
118 		sb.append("<B>tvlist <i>ZIPCODE</i></B> (displays the tv providers in the area of the zipcode)\n");
119 		sb.append("* If the preference \"zipcode\" is set, you can omit the zipcode to use your default.\n");
120 		sb
121 			.append("<B>tv <i>SHOW<i></B> (displays the synopsis of the next upcomming episode of the specified show in your area)\n");
122 		sb.append("* The preference \"tvprovider\" must be set!");
123 		return sb.toString();
124 	}
125 
126 
127 	/***
128 	 * Grab show detail, or list the providers
129 	 * 
130 	 * @param buddy
131 	 *            the buddy
132 	 * @param query
133 	 *            the request
134 	 */
135 	public void performService(AIMBuddy buddy, String query) {
136 		if (query.trim().toLowerCase().startsWith("tvlist")) {
137 			StringTokenizer st = new StringTokenizer(query, " ");
138 			String zipPref = buddy.getPreference("zipcode");
139 			if ((zipPref == null) && (st.countTokens() < 2)) {
140 				super.sendMessage(buddy, "ERROR:\n" + help());
141 			} else {
142 				String imcommand = st.nextToken();
143 				String zipcode = "";
144 				if (st.hasMoreElements()) {
145 					zipcode = ((String) st.nextElement()).trim();
146 				} else if (zipPref != null) {
147 					zipcode = zipPref;
148 				}
149 				String result = "Couldn't get provider list.";
150 				if (zipcode.length() == 5) {
151 					TVProviderScraper scrapper = new TVProviderScraper(zipcode);
152 					Map providers = scrapper.getProviders();
153 
154 					if (providers.size() > 0) {
155 						result = "Set the preference <b>tvprovider</b> to your providers code.\n";
156 						//seemed to have gotten a decent result, so display it
157 						for (Iterator iter = providers.keySet().iterator(); iter.hasNext();) {
158 							String key = (String) iter.next();
159 							String value = (String) providers.get(key);
160 							logger.fine(value + "\t use --> " + key);
161 							result += (value + "\t --> <b>" + key + "</b>\n");
162 						}
163 						if (zipPref == null) {
164 							//if the buddy didnt have a zippref set, set this
165 							// one for him
166 							buddy.setPreference("zipcode", zipcode);
167 						}
168 					}
169 				}
170 				super.sendMessage(buddy, result);
171 			}
172 		} else if (query.trim().toLowerCase().startsWith("tv")) {
173 			StringTokenizer st = new StringTokenizer(query, " ");
174 			String provider = buddy.getPreference("tvprovider");
175 			if ((provider == null) || provider.trim().equals("")) {
176 				String message = "ERROR: You must set the <b>tvprovider</b> preference first!\n";
177 				message += "See help for the TV Module and the Preference Module.";
178 				super.sendMessage(buddy, message);
179 			} else if (st.countTokens() < 2) {
180 				super.sendMessage(buddy, "ERROR:\n" + help());
181 			} else {
182 				String imcommand = st.nextToken();
183 				String searchString = "";
184 				while (st.hasMoreElements()) {
185 					searchString += (((String) st.nextElement()).trim() + " ");
186 				}
187 
188 				TVListingsScraper listings = new TVListingsScraper(searchString, provider);
189 				ShowDetail showDetail = listings.getShowDetail();
190 
191 				String result = "Unable to find show information for " + searchString;
192 
193 				if ((showDetail != null) && (showDetail.getUrl() != null) && (showDetail.getShow() != null)) {
194 					TVResultsScraper scrapper = new TVResultsScraper(showDetail.getUrl(), showDetail.getShow());
195 					String summary = scrapper.getSummary();
196 					String channelInfo = "";
197 					if (showDetail.getChannel() != null) {
198 						channelInfo = showDetail.getChannel() + " ";
199 					}
200 					if (summary.trim().equals("")) {
201 						result = "<a href=\"" + showDetail.getUrl() + "\">" + showDetail.getShow() + "</a>";
202 					} else {
203 						result = showDetail.getShow() + "\n" + channelInfo + summary;
204 					}
205 				}
206 
207 				super.sendMessage(buddy, result);
208 			}
209 		}
210 	}
211 }
212 
213 class TVProviderScraper extends ParserCallback {
214 	static Logger logger = Logger.getLogger(TVListingsModule.class.getName());
215 	private static final String BASE_URL = "http://tv.yahoo.com/lineup?co=us&.intl=us&zip=";
216 	private String zipcode;
217 	private List codes;
218 	private List names;
219 	private boolean validSelect = false;
220 	private boolean validText = false;
221 
222 
223 	/***
224 	 * Contructor for TVResultsScraper
225 	 * 
226 	 * @param zipcode
227 	 */
228 	public TVProviderScraper(String zipcode) {
229 		this.zipcode = zipcode;
230 		this.codes = new ArrayList();
231 		this.names = new ArrayList();
232 	}
233 
234 
235 	/***
236 	 * Look for "lineup" dropdown and its values
237 	 * 
238 	 * @param t
239 	 *            select or option tag
240 	 * @param a
241 	 *            looks for name
242 	 * @param pos
243 	 *            not used
244 	 */
245 	public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
246 		if ((t == HTML.Tag.SELECT) && (a.getAttribute(HTML.Attribute.NAME) != null)
247 			&& a.getAttribute(HTML.Attribute.NAME).equals("lineup")) {
248 			validSelect = true;
249 		} else if ((t == HTML.Tag.OPTION) && validSelect && (a.getAttribute(HTML.Attribute.VALUE) != null)) {
250 			String code = a.getAttribute(HTML.Attribute.VALUE).toString();
251 			codes.add(code);
252 			validText = true;
253 		}
254 	}
255 
256 
257 	/***
258 	 * Looks for the end of select and option tags
259 	 * 
260 	 * @param t
261 	 *            select or option
262 	 * @param pos
263 	 *            unused
264 	 */
265 	public void handleEndTag(Tag t, int pos) {
266 		if (t == HTML.Tag.SELECT) {
267 			validSelect = false;
268 		} else if (t == HTML.Tag.OPTION) {
269 			validText = false;
270 		}
271 	}
272 
273 
274 	/***
275 	 * Grab the providers when appropriate
276 	 * 
277 	 * @param data
278 	 *            the text
279 	 * @param pos
280 	 *            unused
281 	 */
282 	public void handleText(char[] data, int pos) {
283 		if (validText) {
284 			String line = new String(data).trim();
285 
286 			//ignore the time zone providers
287 			if ((line.indexOf("Time Zone") == -1) && (line.indexOf("None") == -1)) {
288 				names.add(line);
289 			} else {
290 				codes.remove(codes.size() - 1);
291 			}
292 		}
293 	}
294 
295 
296 	/***
297 	 * Uses the parser to return a map of appropriate providers
298 	 * 
299 	 * @return map of providers keyed by codes
300 	 */
301 	public Map getProviders() {
302 		HashMap result = new HashMap();
303 		try {
304 			URL url = new URL(BASE_URL + this.zipcode);
305 			logger.fine("Looking for prividers for:" + zipcode + " using URL= " + url);
306 			URLConnection conn = url.openConnection();
307 			BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
308 			ParserDelegator pd = new ParserDelegator();
309 			pd.parse(br, this, true);
310 			if (codes.size() == names.size()) {
311 				for (int i = 0; i < codes.size(); i++) {
312 					result.put(codes.get(i), names.get(i));
313 				}
314 			}
315 		} catch (Exception e) {
316 			e.printStackTrace();
317 			return new HashMap();
318 		}
319 		return result;
320 	}
321 
322 
323 	/***
324 	 * Testing method
325 	 * 
326 	 * @param args
327 	 *            zipcode
328 	 */
329 	public static void main(String[] args) {
330 		String zip = args[0];
331 		TVProviderScraper scrapper = new TVProviderScraper(zip);
332 		Map providers = scrapper.getProviders();
333 		for (Iterator iter = providers.keySet().iterator(); iter.hasNext();) {
334 			String key = (String) iter.next();
335 			String value = (String) providers.get(key);
336 			System.out.println(value + "\t use --> " + key);
337 		}
338 	}
339 }
340 
341 class TVResultsScraper extends ParserCallback {
342 	static Logger logger = Logger.getLogger(TVListingsModule.class.getName());
343 	private URL url;
344 	private String show;
345 	boolean useText = false;
346 	boolean inLink = false;
347 	boolean ignore = false;
348 	boolean foundStart = false;
349 	boolean foundEnd = false;
350 	int showCount = 2;
351 	private StringBuffer result;
352 
353 
354 	/***
355 	 * Contructor for TVResultsScraper
356 	 * 
357 	 * @param url
358 	 * @param show
359 	 */
360 	public TVResultsScraper(URL url, String show) {
361 		this.url = url;
362 		this.show = show;
363 		this.result = new StringBuffer();
364 	}
365 
366 
367 	/***
368 	 * Looks for start of links, paragraphs and HRs
369 	 * 
370 	 * @param t
371 	 *            the tag
372 	 * @param a
373 	 *            unused
374 	 * @param pos
375 	 *            unused
376 	 */
377 	public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
378 		if (t == HTML.Tag.HR) {
379 			useText = false;
380 		} else if (t == HTML.Tag.A) {
381 			inLink = true;
382 		} else if ((t == HTML.Tag.P) && useText) {
383 			result.append("\n");
384 		} else if ((t == HTML.Tag.TABLE) && result.length() > 0) {
385             useText=false;
386             foundEnd=true;
387 		}
388 	}
389 
390 
391 	/***
392 	 * Looks for end of links
393 	 * 
394 	 * @param t
395 	 *            A tag
396 	 * @param pos
397 	 *            unused
398 	 */
399 	public void handleEndTag(Tag t, int pos) {
400 		if (t == HTML.Tag.A) {
401 			inLink = false;
402 		}
403 	}
404 
405 
406 	/***
407 	 * Grabs the show info
408 	 * 
409 	 * @param data
410 	 *            the text
411 	 * @param pos
412 	 *            unused
413 	 */
414 	public void handleText(char[] data, int pos) {
415 		String line = new String(data).trim();
416 		if (!ignore && line.startsWith(this.show)) {
417 			if (--showCount < 0) {
418 				foundStart = useText = ignore = true;
419 			}
420 		} else if (line.startsWith("Original Airdate:")) {
421 			useText = false;
422 			foundEnd = true;
423 			result.append(line + " ");
424 		} else if (line.startsWith("Future Airings:")) {
425 			useText = false;
426 			foundEnd = true;
427 		} else if (useText && !inLink && !line.equals("")) {
428 			result.append(line + " ");
429 		}
430 	}
431 
432 
433 	private String getResult() {
434 		return result.toString();
435 	}
436 
437 
438 	/***
439 	 * Grabs the summary info
440 	 * 
441 	 * @return The text
442 	 */
443 	public String getSummary() {
444 		logger.fine("Looking for Show:" + show + " using URL= " + url);
445 		String tempResult = "";
446 		try {
447 			URLConnection conn = this.url.openConnection();
448 			BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
449 			ParserDelegator pd = new ParserDelegator();
450 			pd.parse(br, this, true);
451 
452 			tempResult = getResult();
453 			if (!foundStart || !foundEnd) {
454 				tempResult = "";
455 			}
456 		} catch (Exception e) {
457 			//e.printStackTrace();
458 			logger.severe(e.toString());
459 			return "";
460 		}
461 		return tempResult;
462 	}
463 
464 
465 	/***
466 	 * Looks for BRs and appends newlines
467 	 * 
468 	 * @param t
469 	 *            the tag
470 	 * @param a
471 	 *            unused
472 	 * @param pos
473 	 *            unused
474 	 */
475 	public void handleSimpleTag(Tag t, MutableAttributeSet a, int pos) {
476 		if ((t == HTML.Tag.BR) && useText) {
477 			result.append("\n");
478 		}
479 	}
480 }
481 
482 class TVListingsScraper extends ParserCallback {
483 	static Logger logger = Logger.getLogger(TVListingsModule.class.getName());
484 	private static final String BASE_URL = "http://search.tv.yahoo.com";
485 	private static final String SEARCH_URL = "/search/tv";
486 	private static final String SHOW_URL = "?title=";
487 	private static final String PROVIDER_URL = "&type=n&lineup=";
488 	private static final String END_URL = "&search=true&.intl=us&range=7";
489 	private String provider;
490 	private String show;
491 	private boolean foundURL = false;
492 	private boolean foundText = false;
493 	private boolean grabURL = false;
494 	private boolean grabChannel = false;
495 	private ShowDetail result = new ShowDetail();
496 
497 
498 	/***
499 	 * Creates a new TVListingsScraper object.
500 	 * 
501 	 * @param show
502 	 *            shows name
503 	 * @param provider
504 	 *            provider to use for lookup
505 	 */
506 	public TVListingsScraper(String show, String provider) {
507 		this.show = show.replace(' ', '+');
508 		this.provider = provider;
509 	}
510 
511 
512 	/***
513 	 * Grabs the shows URL when its appropriate
514 	 * 
515 	 * @param t
516 	 *            the tag!
517 	 * @param a
518 	 *            looks for hrefs
519 	 * @param pos
520 	 *            unused
521 	 */
522 	public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
523 		if (!foundURL) {
524 			if (grabURL) {
525 				try {
526 					String urlString = a.getAttribute(HTML.Attribute.HREF).toString();
527 					StringTokenizer stok = new StringTokenizer(urlString);
528 					String resultString = "";
529 					while (stok.hasMoreElements()) {
530 						resultString += stok.nextElement();
531 					}
532 
533 					URL url = new URL(resultString);
534 					result.setUrl(url);
535 					foundURL = true;
536 				} catch (Exception e) {
537 				}
538 			}
539 		}
540 	}
541 
542 
543 	/***
544 	 * DOCUMENT ME!
545 	 * 
546 	 * @param t
547 	 *            DOCUMENT ME!
548 	 * @param pos
549 	 *            DOCUMENT ME!
550 	 */
551 	public void handleEndTag(Tag t, int pos) {
552 		if (t == HTML.Tag.A) {
553 			if (foundURL && grabURL) {
554 				grabURL = false;
555 				grabChannel = true;
556 			}
557 		}
558 	}
559 
560 
561 	/***
562 	 * Looks for link to show detail
563 	 * 
564 	 * @param data
565 	 *            the text
566 	 * @param pos
567 	 *            unused
568 	 */
569 	public void handleText(char[] data, int pos) {
570 		if (foundURL && !foundText) {
571 			logger.finer("FOUND TEXT=" + new String(data));
572 			result.setShow(new String(data));
573 			foundText = true;
574 		} else if (new String(data).trim().equals("Chronologically")) {
575 			//grab the first link after the Chronologically link
576 			grabURL = true;
577 		} else if (grabChannel) {
578 			String line = new String(data).trim();
579 			int index = line.indexOf(",");
580 			if (index >= 0) {
581 				grabChannel = false;
582 				result.setChannel(line.substring(0, index));
583 			}
584 		}
585 	}
586 
587 
588 	/***
589 	 * Grabs the show detail
590 	 * 
591 	 * @return the show detail
592 	 */
593 	public ShowDetail getShowDetail() {
594 		try {
595 			URL url = new URL(BASE_URL + SEARCH_URL + SHOW_URL + this.show + PROVIDER_URL + this.provider + END_URL);
596 			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
597 			int resCode = conn.getResponseCode();
598 			logger.fine("THE URL=" + conn.getURL() + " using code:" + resCode);
599 
600 			if (logger.isLoggable(Level.FINEST)) {
601 				Map headers = conn.getHeaderFields();
602 				for (Iterator iter = headers.keySet().iterator(); iter.hasNext();) {
603 					String key = (String) iter.next();
604 					List values = (List) headers.get(key);
605 					for (Iterator iterator = values.iterator(); iterator.hasNext();) {
606 						String value = (String) iterator.next();
607 						logger.finest("Header:" + key + " = " + value);
608 					}
609 				}
610 			}
611 
612 			//handle relative redirects
613 			if ((resCode > 300) && (resCode < 400)) {
614 				String newURL = conn.getHeaderField("Location");
615 				if ((newURL != null) && newURL.startsWith("/")) {
616 					logger.fine("Redirecting to new URL=" + newURL);
617 					url = new URL(BASE_URL + newURL);
618 					conn.disconnect();
619 					conn = (HttpURLConnection) url.openConnection();
620 				}
621 			}
622 			BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
623 
624 			ParserDelegator pd = new ParserDelegator();
625 			pd.parse(br, this, true);
626 		} catch (Exception e) {
627 			e.printStackTrace();
628 			return null;
629 		}
630 		return result;
631 	}
632 }
633 
634 class ShowDetail {
635 	private URL url;
636 	private String show;
637 	private String channel;
638 
639 
640 	/***
641 	 * Get Show name
642 	 * 
643 	 * @return Show name
644 	 */
645 	public String getShow() {
646 		return show;
647 	}
648 
649 
650 	/***
651 	 * Get Show Detail URL
652 	 * 
653 	 * @return Show Detail URL
654 	 */
655 	public URL getUrl() {
656 		return url;
657 	}
658 
659 
660 	/***
661 	 * Set Show name
662 	 * 
663 	 * @param string
664 	 *            the show's name
665 	 */
666 	public void setShow(String string) {
667 		show = string;
668 	}
669 
670 
671 	/***
672 	 * Set the Show Detail URL
673 	 * 
674 	 * @param url
675 	 *            Show Detail URL
676 	 */
677 	public void setUrl(URL url) {
678 		this.url = url;
679 	}
680 
681 
682 	/***
683 	 * Get the Channel the show is on
684 	 * 
685 	 * @return the Channel the show is on
686 	 */
687 	public String getChannel() {
688 		return channel;
689 	}
690 
691 
692 	/***
693 	 * Set the Channel the show is on
694 	 * 
695 	 * @param string
696 	 *            the Channel the show is on
697 	 */
698 	public void setChannel(String string) {
699 		channel = string;
700 	}
701 }