2 * Copyright 2005 the original author or authors.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package org.wamblee.crawler.kiss;
20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.PrintStream;
26 import java.util.ArrayList;
27 import java.util.Date;
28 import java.util.EnumMap;
29 import java.util.List;
30 import java.util.Properties;
32 import java.util.TreeSet;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
36 import javax.mail.Message;
37 import javax.mail.MessagingException;
38 import javax.mail.Session;
39 import javax.mail.Transport;
40 import javax.mail.internet.InternetAddress;
41 import javax.mail.internet.MimeMessage;
43 import org.apache.commons.httpclient.HttpClient;
44 import org.apache.commons.logging.Log;
45 import org.apache.commons.logging.LogFactory;
46 import org.wamblee.crawler.Action;
47 import org.wamblee.crawler.Configuration;
48 import org.wamblee.crawler.Crawler;
49 import org.wamblee.crawler.Page;
50 import org.wamblee.crawler.PageException;
51 import org.wamblee.crawler.impl.ConfigurationParser;
52 import org.wamblee.crawler.impl.CrawlerImpl;
53 import org.wamblee.crawler.kiss.Program.RecordingResult;
56 * The KiSS crawler for automatic recording of interesting TV shows.
59 public class KissCrawler {
61 private static final Log LOG = LogFactory.getLog(KissCrawler.class);
64 * Log file name for the crawler.
66 private static final String LOG_FILE = "kiss.log";
69 * Start URL of the electronic programme guide.
71 private static final String START_URL = "http://epg.kml.kiss-technology.com/login_core.php";
74 * Crawler configuration file.
76 private static final String CRAWLER_CONFIG = "config.xml";
79 * Configuration file describing interesting programs.
81 private static final String PROGRAM_CONFIG = "programs.xml";
84 * Regular expression for matching time interval strings in the retrieved
87 private static final String TIME_REGEX = "([0-9]{2}):([0-9]{2})[^0-9]*([0-9]{2}):([0-9]{2}).*";
90 * Compiled pattern for the time regular expression.
92 private Pattern _pattern;
95 * Runs the KiSS crawler.
98 * Arguments, currently all ignored because they are hardcoded.
100 * In case of problems.
102 public static void main(String[] aArgs) throws Exception {
103 new KissCrawler(START_URL, CRAWLER_CONFIG, PROGRAM_CONFIG);
107 * Constructs the crawler. This retrieves the TV guide by crawling the KiSS
108 * EPG guide, filters the guide for interesting programs, tries to record
109 * them, and sends a summary mail to the user.
112 * Start URL of the electronic programme guide.
113 * @param aCrawlerConfig
114 * Configuration file for the crawler.
115 * @param aProgramConfig
116 * Configuration file describing interesting shows.
117 * @throws IOException
118 * In case of problems reading files.
119 * @throws MessagingException
120 * In case of problems sending a mail notification.
122 public KissCrawler(String aStartUrl, String aCrawlerConfig,
123 String aProgramConfig) throws IOException, MessagingException {
125 _pattern = Pattern.compile(TIME_REGEX);
127 FileOutputStream fos = new FileOutputStream(new File(LOG_FILE));
128 PrintStream os = new PrintStream(fos);
131 HttpClient client = new HttpClient();
132 // client.getHostConfiguration().setProxy("127.0.0.1", 3128);
134 Crawler crawler = createCrawler(aCrawlerConfig, os, client);
135 InputStream programConfigFile = new FileInputStream(new File(
137 List<ProgramFilter> programFilters = new ProgramConfigurationParser()
138 .parse(programConfigFile);
140 Page page = getStartPage(aStartUrl, crawler);
141 TVGuide guide = createGuide(page);
142 PrintVisitor printer = new PrintVisitor(System.out);
143 guide.accept(printer);
146 recordInterestingShows(programFilters, guide);
150 System.out.println("Output written on '" + LOG_FILE + "'");
155 * Records interesting shows.
157 * @param aProgramCondition
158 * Condition determining which shows are interesting.
161 * @throws MessagingException
162 * In case of problems sending a summary mail.
164 private void recordInterestingShows(List<ProgramFilter> aProgramCondition,
165 TVGuide aGuide) throws MessagingException {
167 Set<Program> showsToRecord = new TreeSet<Program>(new Program.TimeSorter());
168 Set<Program> interestingShows = new TreeSet<Program>(new Program.TimeSorter());
170 for (ProgramFilter filter : aProgramCondition) {
171 List<Program> programs = filter.apply(aGuide);
172 switch (filter.getAction()) {
174 for (Program program: programs) {
175 showsToRecord.add(program);
180 for (Program program: programs) {
181 if ( program.isRecordingPossible()) {
182 interestingShows.add(program);
188 throw new RuntimeException("Unknown action '" + filter.getAction() + "'");
193 EnumMap<RecordingResult, List<Program>> messages = recordShows(showsToRecord);
195 String msg = "Summary of KiSS crawler: \n\n\n";
197 for (RecordingResult result : RecordingResult.values()) {
198 if (messages.get(result).size() > 0) {
199 msg += result.getDescription() + "\n\n";
200 for (Program program : messages.get(result)) {
201 msg += program + "\n\n";
206 if ( interestingShows.size() > 0 ) {
207 msg += "Possibly interesting shows:\n\n";
208 for (Program program: interestingShows) {
209 msg += program + "\n\n";
212 if (showsToRecord.size() + interestingShows.size() == 0) {
213 msg += "No suitable programs found";
216 System.out.println(msg);
222 * @param showsToRecord Shows to record.
223 * @return Recording results.
225 private EnumMap<RecordingResult, List<Program>> recordShows(Set<Program> showsToRecord) {
226 EnumMap<RecordingResult, List<Program>> messages = new EnumMap<RecordingResult, List<Program>>(
227 RecordingResult.class);
228 for (RecordingResult result : RecordingResult.values()) {
229 messages.put(result, new ArrayList<Program>());
232 for (Program program : showsToRecord) {
233 Program.RecordingResult result = program.record();
234 messages.get(result).add(program);
240 * Creates the crawler.
242 * @param aCrawlerConfig
243 * Crawler configuration file.
245 * Logging output stream for the crawler.
247 * HTTP Client to use.
249 * @throws FileNotFoundException
250 * In case configuration files cannot be found.
252 private Crawler createCrawler(String aCrawlerConfig, PrintStream aOs,
253 HttpClient aClient) throws FileNotFoundException {
254 ConfigurationParser parser = new ConfigurationParser(aOs);
255 InputStream crawlerConfigFile = new FileInputStream(new File(
257 Configuration config = parser.parse(crawlerConfigFile);
258 Crawler crawler = new CrawlerImpl(aClient, config);
263 * Gets the start page of the electronic programme guide. This involves
264 * login and navigation to a suitable start page after logging in.
267 * URL of the electronic programme guide.
270 * @return Starting page.
272 private Page getStartPage(String aStartUrl, Crawler aCrawler) {
274 Page page = aCrawler.getPage(aStartUrl);
275 return page.getAction("channels-favorites").execute();
276 } catch (PageException e) {
277 throw new RuntimeException(
278 "Could not login to electronic program guide", e);
283 * Creates the TV guide by web crawling.
289 private TVGuide createGuide(Page aPage) {
290 LOG.info("Obtaining full TV guide");
291 Action[] actions = aPage.getActions();
292 List<Channel> channels = new ArrayList<Channel>();
293 for (Action action : actions) {
295 LOG.info("Getting channel info for '" + action.getName() + "'");
296 Channel channel = createChannel(action.getName(), action
297 .execute().getAction("right-now").execute());
298 channels.add(channel);
299 if (SystemProperties.isDebugMode()) {
300 break; // Only one channel is crawled.
302 } catch (PageException e) {
303 LOG.error("Could not create channel information for '"
304 + action.getName() + "'", e);
307 return new TVGuide(channels);
311 * Create channel information for a specific channel.
316 * Starting page for the channel.
319 private Channel createChannel(String aChannel, Page aPage) {
320 LOG.info("Obtaining program for " + aChannel);
321 Action[] programActions = aPage.getActions();
322 List<Program> programs = new ArrayList<Program>();
323 for (Action action : programActions) {
324 String time = action.getContent().element("time").getText().trim();
325 Matcher matcher = _pattern.matcher(time);
326 if (matcher.matches()) {
327 Time begin = new Time(Integer.parseInt(matcher.group(1)),
328 Integer.parseInt(matcher.group(2)));
329 Time end = new Time(Integer.parseInt(matcher.group(3)), Integer
330 .parseInt(matcher.group(4)));
331 TimeInterval interval = new TimeInterval(begin, end);
332 String description = "";
333 String keywords = "";
334 if (!SystemProperties.isNoProgramDetailsRequired()) {
336 Page programInfo = action.execute();
337 description = programInfo.getContent().element(
338 "description").getText().trim();
339 keywords = programInfo.getContent().element("keywords")
341 } catch (PageException e) {
343 .warn("Program details coul dnot be determined for '"
344 + action.getName() + "'");
347 Program program = new Program(aChannel, action.getName(),
348 description, keywords, interval, action);
350 LOG.debug("Got program " + program);
351 programs.add(program);
354 return new Channel(aChannel, programs);
358 * Sends a summary mail to the user.
362 * @throws MessagingException
363 * In case of problems sending mail.
365 private void sendMail(String aText) throws MessagingException {
366 Properties props = new Properties();
367 props.put("mail.transport.protocol", "smtp");
368 props.put("mail.smtp.host", "falcon");
369 props.put("mail.smtp.port", "25");
371 Session mailSession = Session.getInstance(props);
372 Message message = new MimeMessage(mailSession);
374 message.setFrom(new InternetAddress("erik@brakkee.org"));
375 message.setRecipient(Message.RecipientType.TO, new InternetAddress(
376 "erik@brakkee.org"));
377 message.setSentDate(new Date());
378 message.setSubject("KiSS crawler update");
379 message.setText(aText);
380 Transport.send(message);