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  
34  package com.levelonelabs.aimbot.modules;
35  
36  
37  import java.io.BufferedReader;
38  import java.io.InputStreamReader;
39  
40  import java.net.URL;
41  import java.net.URLConnection;
42  
43  import java.util.ArrayList;
44  import java.util.StringTokenizer;
45  import java.util.logging.Logger;
46  
47  import javax.swing.text.MutableAttributeSet;
48  import javax.swing.text.html.HTML;
49  import javax.swing.text.html.HTML.Tag;
50  import javax.swing.text.html.HTMLEditorKit.ParserCallback;
51  import javax.swing.text.html.parser.ParserDelegator;
52  
53  import com.levelonelabs.aim.AIMBuddy;
54  import com.levelonelabs.aimbot.AIMBot;
55  import com.levelonelabs.aimbot.BotModule;
56  
57  
58  /***
59   * Handles requests to get movie times based on zip and/or theater and lists theaters by zip code.
60   *
61   * @author John G Ledo <A HREF="mailto:john@imssoft.com">john@imssoft.com</A>
62   *
63   * @created October 3, 2003
64   */
65  public class MoviesModule extends BotModule {
66      static Logger logger=Logger.getLogger(MoviesModule.class.getName());
67      private static ArrayList services;
68  
69      /***
70       * Initialize the service commands.
71       */
72      static {
73          services=new ArrayList();
74          services.add("movies");
75          services.add("movieslegend");
76          services.add("theaters");
77      }
78  
79      /***
80       * Constructor for MoviesModule.
81       *
82       * @param bot
83       */
84      public MoviesModule(AIMBot bot) {
85          super(bot);
86      }
87  
88      /***
89       * @see com.levelonelabs.aimbot.BotModule#getName()
90       */
91      public String getName() {
92          return "Movies Module";
93      }
94  
95  
96      /***
97       * @see com.levelonelabs.aimbot.BotModule#getServices()
98       */
99      public ArrayList getServices() {
100         return services;
101     }
102 
103 
104     /***
105      * @see com.levelonelabs.aimbot.BotModule#help()
106      */
107     public String help() {
108         StringBuffer sb=new StringBuffer();
109         sb.append("<B>theaters <i>ZIPCODE<i></B> (displays a list of theaters in your area)\n");
110         sb.append(
111             "* If the preference \"zipcode\" is set, you can omit the zipcode to use your default.\n");
112         sb.append(
113             "<B>movies <i>ZIPCODE [Theater Number]</i></B> (displays movie times for the theater closest to your zipcode if a theater number is not provided)\n");
114         sb.append(
115             "* If the preference \"zipcode\" is set, you can omit the zipcode to use your default.\n");
116         sb.append("<B>movieslegend</B> (displays the legend for movie times)");
117         return sb.toString();
118     }
119 
120 
121     /***
122      * Grab the movie times (or list the theater not done)
123      *
124      * @param buddy the buddy
125      * @param query the request
126      */
127     public void performService(AIMBuddy buddy, String query) {
128         StringTokenizer st=new StringTokenizer(query, " ");
129         String zipPref=buddy.getPreference("zipcode");
130         String zipMovie=buddy.getPreference("movieszipcode");
131         String imcommand=st.nextToken();
132         String zipcode="";
133         String theatercode="";
134 
135         if(imcommand.trim().equalsIgnoreCase("MOVIES")) {
136             /* If there are three tokens then it's command, zip, theater.
137              * If there are only two then the second can either be theater
138              * or zip code (I check by length).  If the second is a theater
139              * then I first use the zip from the theater search done to get
140              * that number.  If there is no zip, because they went of memeory
141              * or used their default then I use the zipcode pref.  If there
142              * are no tocken then I only use the zipcode pref not the zip
143              * saved if they did a theater search (bad? not sure).
144              */
145             if(st.countTokens() == 2) {
146                 zipcode=((String) st.nextElement()).trim();
147                 theatercode=((String) st.nextElement()).trim();
148             } else {
149                 if(st.hasMoreElements()) {
150                     zipcode=((String) st.nextElement()).trim();
151                     if(zipcode.length() == 1) {
152                         theatercode=zipcode;
153                         zipcode=zipMovie;
154                         if((zipcode == null) || (zipcode.length() != 5)) {
155                             zipcode=zipPref;
156                         }
157                     }
158                 } else if(zipPref != null) {
159                     zipcode=zipPref;
160                 }
161             }
162 
163             if( zipcode!=null && zipcode.length() == 5) {
164                 MovieTimesScraper scrapper;
165                 if(theatercode.length() == 1) {
166                     scrapper=new MovieTimesScraper(zipcode, theatercode);
167                 } else {
168                     scrapper=new MovieTimesScraper(zipcode);
169                 }
170                 if(zipPref == null) {
171                     //if the buddy didnt have a zippref set, set this one for him
172                     buddy.setPreference("zipcode", zipcode);
173                 }
174                 Object[] list=scrapper.getTimes().toArray();
175                 if((list.length < 1) || (((String) list[0]).length() < 1)) {
176                     super.sendMessage(buddy, "Could not find movie times.");
177                     return;
178                 }
179                 for(int i=0; i < list.length; i++) {
180                     logger.fine("Sending movie message #"+i);
181                     super.sendMessage(buddy, ((String) list[i]));
182                 }
183             } else {
184                 super.sendMessage(buddy, "ERROR:\n"+help());
185             }
186         } else if(imcommand.trim().equalsIgnoreCase("THEATERS")) {
187             if(st.hasMoreElements()) {
188                 zipcode=((String) st.nextElement()).trim();
189             } else if(zipPref != null) {
190                 zipcode=zipPref;
191             }
192             if(zipcode.length() == 5) {
193                 buddy.setPreference("movieszipcode", zipcode);
194                 MovieTheaterScraper scrapper=new MovieTheaterScraper(zipcode);
195                 if(zipPref == null) {
196                     //If the buddy didnt have a zippref set, set this one for him
197                     buddy.setPreference("zipcode", zipcode);
198                 }
199                 String list=scrapper.getTheaters();
200                 if((list == null) || (list.length() < 1)) {
201                     super.sendMessage(buddy, "Could not find theaters.");
202                 } else {
203                     list="<B>Theaters for "+zipcode+"</B>\n"+list;
204                     list=list+
205                         "\n\nSend <b>movies <i>theaternumber</i></b> to obtain movie times for that theater.\n";
206                     super.sendMessage(buddy, list);
207                 }
208             } else {
209                 super.sendMessage(buddy, "ERROR:\n"+help());
210             }
211         } else if(imcommand.trim().equalsIgnoreCase("MOVIESLEGEND")) {
212             super.sendMessage(buddy,
213                 "<b>Legend:</b>\n"+"( ) - discounted / bargain shows.\n"+
214                 "* - Sony Digital Dynamic Sound (SDDS)\n"+"** - Digital Theater Systems (DTS)\n"+
215                 "*** - Dolby Digital Surround EX\n"+"**** - Dolby Digital\n"+
216                 "***** - Dolby Stereo\n");
217         }
218     }
219 }
220 
221 
222 
223 class MovieTimesScraper extends ParserCallback {
224     static Logger logger=Logger.getLogger(MoviesModule.class.getName());
225     private String urlPart="http://movies.yahoo.com/showtimes/showtimes.html?m=&t=&a=&s=tm&r=sim&p=0&get=Get+Showtimes&z=";
226     private String zipcode;
227     private String line;
228     private int stage;
229     private String theater;
230     private String theaterNext;
231     private boolean areLinkTimes;
232 
233     // Results are kept in ArrayList because they tend to exceed
234     // AOL's buffer size so we have to send a couple of posts
235     private ArrayList result;
236 
237     /***
238      * Contructor for MovieTimesScraper
239      *
240      * @param zipcode
241      */
242     public MovieTimesScraper(String zipcode) {
243         this.zipcode=zipcode;
244         this.result=new ArrayList();
245         this.line="";
246         this.theater="0";
247         this.theaterNext="1";
248         this.stage=0;
249         this.areLinkTimes=false;
250     }
251 
252 
253     /***
254      * Contructor for MovieTimesScraper
255      *
256      * @param zipcode
257      * @param theater
258      */
259     public MovieTimesScraper(String zipcode, String theater) {
260         this.zipcode=zipcode;
261         this.result=new ArrayList();
262         this.line="";
263         this.stage=0;
264         this.areLinkTimes=false;
265         try {
266             int num=Integer.parseInt(theater);
267             this.theater=theater;
268             this.theaterNext=String.valueOf((num+1));
269         } catch(Exception ex) {
270             this.theater="0";
271             this.theaterNext="1";
272         }
273     }
274 
275     /***
276      * Looks for link 'T0' for start of first theater.  Then scrape text until T1 link.
277      *
278      * @param t the tag
279      * @param a unused
280      * @param pos unused
281      */
282     public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
283         if((t == HTML.Tag.A) && (a.getAttribute(HTML.Attribute.NAME) != null) &&
284                 a.getAttribute(HTML.Attribute.NAME).equals("T"+theater)) {
285             stage=1;
286         } else if((t == HTML.Tag.A) && (stage == 1)) {
287             stage=2;
288         } else if((t == HTML.Tag.A) && (a.getAttribute(HTML.Attribute.NAME) != null) &&
289                 a.getAttribute(HTML.Attribute.NAME).equals("T"+theaterNext)) {
290             stage=5;
291         }
292     }
293 
294 
295     /***
296      * Looks for links
297      *
298      * @param t A tag
299      * @param pos unused
300      */
301     public void handleEndTag(Tag t, int pos) {
302         if((t == HTML.Tag.A) && (stage == 2)) {
303             stage=3;
304         } else if((t == HTML.Tag.A) && (stage == 3)) {
305             stage=4;
306         } else if((t == HTML.Tag.HTML) && (line.length() > 0)) {
307             // End of document so add last line to the arraylist
308             result.add(line);
309             line="";
310         } else if(t == HTML.Tag.TD) {
311             if(areLinkTimes) {
312                 areLinkTimes=false;
313                 line=line+"\n";
314             }
315             if(stage == 4) {
316                 stage=3;
317             }
318         }
319     }
320 
321 
322     /***
323      * Grabs the theater text
324      *
325      * @param data the text
326      * @param pos unused
327      */
328     public void handleText(char[] data, int pos) {
329         String temp=new String(data);
330         if(stage == 2 && !temp.equals("Add to My Favorite Theaters")) {
331             line="\n<B>"+temp+"</B>\n";
332         } else if((stage == 3) && (temp.length() > 1) &&
333                 (temp.toUpperCase().indexOf("CLICK") == -1) &&
334                 (temp.toUpperCase().indexOf(" ]") == -1) &&
335                 (temp.toUpperCase().indexOf(" |") == -1) &&
336                 (temp.toUpperCase().indexOf("THEATER INFO") == -1) &&
337                 (temp.toUpperCase().indexOf("MAP IT") == -1)) {
338             if(temp.toUpperCase().indexOf("BUY TICKETS") != -1) {
339                 /* Some theaters sell ticket online so each time is a link
340                  * instead of one long string. Flag is set to create the
341                  * one string.
342                  */
343                 areLinkTimes=true;
344             } else {
345                 if((line.length()+data.length) > 950) {
346                     result.add(line);
347                     line="<B>"+temp+"</B>";
348                 } else {
349                     line=line+"<B>"+temp+"</B>";
350                 }
351                 if(!areLinkTimes) {
352                     line=line+"\n";
353                 }
354             }
355         } else if((stage == 4) && (temp.length() > 1) &&
356                 (temp.toUpperCase().indexOf("CLICK") == -1) &&
357                 (temp.toUpperCase().indexOf(" |") == -1) &&
358                 (temp.toUpperCase().indexOf("THEATER INFO") == -1) &&
359                 (temp.toUpperCase().indexOf("MAP IT") == -1)) {
360             if(temp.toUpperCase().indexOf("BUY TICKETS") != -1) {
361                 /* Some theaters sell ticket online so each time is a link
362                  * instead of one long string. Flag is set to create the
363                  * one string.
364                  */
365                 areLinkTimes=true;
366             } else {
367                 if((line.length()+data.length) > 950) {
368                     result.add(line);
369                     line=temp;
370                 } else {
371                     line=line+temp;
372                 }
373                 if(!areLinkTimes) {
374                     line=line+"\n";
375                 }
376             }
377         }
378     }
379 
380 
381     /***
382      * Grabs the movie times
383      *
384      * @return The text
385      */
386     public ArrayList getTimes() {
387         try {
388             URL url=new URL(urlPart+zipcode);
389             logger.fine("Looking for movies for: "+zipcode+" using URL="+url);
390             URLConnection conn=url.openConnection();
391             BufferedReader br=new BufferedReader(new InputStreamReader(conn.getInputStream()));
392             ParserDelegator pd=new ParserDelegator();
393             pd.parse(br, this, true);
394         } catch(Exception e) {
395             e.printStackTrace();
396         }
397         return this.result;
398     }
399 }
400 
401 
402 
403 class MovieTheaterScraper extends ParserCallback {
404     static Logger logger=Logger.getLogger(MoviesModule.class.getName());
405     private String urlPart="http://movies.yahoo.com/showtimes/showtimes.html?m=&t=&a=&s=tm&r=sim&p=0&get=Get+Showtimes&z=";
406     private String zipcode;
407     private String result;
408     private String line;
409     private boolean startA=false;
410     private boolean createList=true;
411 
412     /***
413      * Contructor for MovieTimesScraper
414      *
415      * @param zipcode
416      */
417     public MovieTheaterScraper(String zipcode) {
418         this.zipcode=zipcode;
419         this.result="";
420         this.line="";
421     }
422 
423     /***
424      * Looks for links 'T0' to 'T9' and creates a list
425      *
426      * @param t the tag
427      * @param a unused
428      * @param pos unused
429      */
430     public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
431         if((a.getAttribute(HTML.Attribute.HREF) != null) &&
432                 a.getAttribute(HTML.Attribute.HREF).equals("#T10")) {
433             createList=false;
434         }
435 
436         if((t == HTML.Tag.A) && (a.getAttribute(HTML.Attribute.HREF) != null) &&
437                 (((String) a.getAttribute(HTML.Attribute.HREF)).startsWith("#T")) && createList) {
438             startA=true;
439             line=a.getAttribute(HTML.Attribute.HREF)+" ";
440         }
441     }
442 
443 
444     /***
445      * Looks for links
446      *
447      * @param t A tag
448      * @param pos unused
449      */
450     public void handleEndTag(Tag t, int pos) {
451         if((t == HTML.Tag.A) && startA) {
452             startA=false;
453         }
454     }
455 
456 
457     /***
458      * Grabs the theater text
459      *
460      * @param data the text
461      * @param pos unused
462      */
463     public void handleText(char[] data, int pos) {
464         if(createList && startA) {
465             result=result+line.substring(2)+": "+new String(data)+"\n";
466         }
467     }
468 
469 
470     /***
471      * Grabs a list of theaters
472      *
473      * @return The text
474      */
475     public String getTheaters() {
476         try {
477             URL url=new URL(urlPart+zipcode);
478             logger.fine("Looking for theaters for: "+zipcode+" using URL="+url);
479             URLConnection conn=url.openConnection();
480             BufferedReader br=new BufferedReader(new InputStreamReader(conn.getInputStream()));
481             ParserDelegator pd=new ParserDelegator();
482             pd.parse(br, this, true);
483         } catch(Exception e) {
484             e.printStackTrace();
485             return result;
486         }
487         return result;
488     }
489 }