1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
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
165
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
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
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
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
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 }