| | | |
| 1 | 1 | | #! /usr/bin/env python |
| 2 | 2 | | # |
| 3 | 3 | | # Author: Ben Holroyd <holroyd.ben@gmail.com> |
| 4 | 4 | | # License: GPL 3.0+ |
| 5 | 5 | | # |
| 6 | 6 | | # Usage: |
| 7 | 7 | | # Put an entry in ~/.config/openbox/menu.xml: |
| 8 | 8 | | # <menu id="Listing" label="listing" execute="~/.config/openbox/scripts/tvlisting" /> |
| 9 | 9 | | #called without args just checks that listings are up to date for that day |
| 10 | 10 | | #-t prints in plain text to stdout |
| 11 | 11 | | #-p is for use in pipe menus |
| 13 | 13 | | from HTMLParser import HTMLParser |
| 14 | 14 | | from urllib import FancyURLopener |
| 15 | 15 | | from threading import Thread |
| 16 | 16 | | import datetime,os |
| 17 | 17 | | from optparse import OptionParser |
| 19 | 19 | | #dict of channels with channel codes, need to get from radio times website |
| 20 | 20 | | channellistdict = {'BBC1':'92','BBC2':'105','ITV':'26','Channel4':'132','Five':'134'} |
| 21 | 21 | | #need a separate list, as dict gets out of order |
| 22 | 22 | | channellist=['BBC1','BBC2','ITV','Channel4','Five'] |
| 23 | 23 | | #where temporary files are kept |
| 24 | 24 | | tmpfile = "/tmp" |
| 26 | 26 | | #dont touch |
| 27 | 27 | | tmpfile=os.path.join(tmpfile,"%%s-%s" % datetime.date.today()) |
| 28 | 28 | | address="http://www.radiotimes.com/ListingsServlet?event=4&jspGridLocation=%%2Fjsp%%2Ftv_listings_grid.jsp&jspListLocation=%%2Fjsp%%2Ftv_listings_single.jsp&jspError=%%2Fjsp%%2Ferror.jsp&listingsFormat=L&channels=%s" |
| 30 | | - | class parser(HTMLParser): |
| 30 | + | class pageparser(HTMLParser): |
| 31 | 31 | | def __init__(self, data, channel): |
| 32 | 32 | | """parse downloaded pages, and save by channel in tmp""" |
| 33 | 33 | | HTMLParser.__init__(self) |
| 34 | 34 | | self.bltime = False |
| 35 | 35 | | self.bltitle = False |
| 36 | 36 | | self.time = '' |
| 37 | 37 | | #hack-- htmlparser doesnt like original form, even though its in comments |
| 38 | 38 | | data = data.replace("</scr'+'ipt>","</script>") |
| 39 | 39 | | self.file = open(tmpfile % channel, 'w') |
| 40 | 40 | | self.feed(data) |
| 42 | 42 | | def handle_starttag(self, tag, attrs): |
| 43 | 43 | | if tag == 'p' and attrs: |
| 44 | 44 | | if attrs[0][1] == 'startTime': |
| 45 | 45 | | self.bltime = True |
| 46 | 46 | | if attrs[0][1] == 'progTitle': |
| 47 | 47 | | self.bltitle = True |
| 49 | 49 | | def handle_data(self,data): |
| 50 | 50 | | if self.bltime == True: |
| 51 | 51 | | self.time = data |
| 52 | 52 | | self.bltime = False |
| 53 | 53 | | if self.bltitle == True: |
| 54 | 54 | | self.file.write('%s%%%s\n' % (data, self.time)) |
| 55 | 55 | | #print "Title: ",data, 'time: ', self.time |
| 56 | 56 | | self.bltitle = False |
| 58 | 58 | | class downloader(Thread): |
| 59 | 59 | | """threaded downloader for webpages""" |
| 60 | 60 | | def __init__ (self, url, channelcode, channel): |
| 61 | 61 | | Thread.__init__(self) |
| 62 | 62 | | self.channel = channel |
| 63 | 63 | | self.channelcode = channelcode |
| 64 | 64 | | self.url = url |
| 66 | 66 | | def run(self): |
| 67 | 67 | | try: |
| 68 | 68 | | data = FancyURLopener().open(self.url % self.channelcode).read() |
| 69 | 69 | | except IOError: |
| 70 | 70 | | pass |
| 71 | | - | parser(data, self.channel) |
| 71 | + | pageparser(data, self.channel) |
| 73 | 73 | | class printer(): |
| 74 | 74 | | """print parsed files""" |
| 75 | 75 | | def __init__(self, channel): |
| 76 | 76 | | self.now = datetime.datetime.now() |
| 77 | 77 | | self.channel = channel |
| 78 | 78 | | #check we have listings for the correct day |
| 79 | 79 | | if not os.path.exists(tmpfile % self.channel): |
| 80 | 80 | | downloader(address,channellistdict[self.channel],self.channel).start() |
| 82 | 82 | | file = open(tmpfile % self.channel, 'r').read() |
| 83 | 83 | | data = [line.split('%') for line in file.split('\n') if len(line) > 0] |
| 84 | 84 | | #find most recent program, then trim 'data' to fit. |
| 85 | 85 | | for index, [programme, time] in enumerate(data): |
| 86 | 86 | | if self.comparetime(time): |
| 87 | 87 | | if index == 0: index = 1 #just in case a programme starts just before midnight |
| 88 | 88 | | self.data = data[index-1:] |
| 89 | 89 | | break |
| 91 | 91 | | def comparetime (self,cmp_time): |
| 92 | 92 | | """returns true if time supplied is bigger than current time""" |
| 93 | 93 | | hour, minute = self.now.hour, self.now.minute |
| 94 | 94 | | #converts 'hh:mm a/pm' to int, pam strips pm and am from string |
| 95 | 95 | | cmp_hour,cmp_minute = map(int, cmp_time.rstrip('pam').split(':')) |
| 96 | 96 | | #convert to 24hr time, & sort 12am, 12pm |
| 97 | 97 | | if cmp_time.endswith('pm') and cmp_hour != 12: cmp_hour += 12 |
| 98 | 98 | | if cmp_time.endswith('am') and cmp_hour == 12: cmp_hour -= 12 |
| 99 | 99 | | return (cmp_hour, cmp_minute) > (hour, minute) |
| 101 | 101 | | def showxmlmenu(self): |
| 102 | 102 | | print " <menu id=\"%s\" label=\"%s\">" % (self.channel,self.channel) |
| 103 | 103 | | print " <item label=\"Now: %s %s\"/>" % (self.data[0][0],self.data[0][1]) |
| 104 | 104 | | print " <item label=\"Next: %s %s\"/>" % (self.data[1][0],self.data[1][1]) |
| 105 | 105 | | print " <menu id=\"Then%s\" label=\"Then\">" % (self.channel) |
| 106 | 106 | | for programme, time in self.data[2:]: |
| 107 | 107 | | print " <item label=\"%s (%s)\"/>" % (programme,time) |
| 108 | 108 | | print " </menu>" |
| 109 | 109 | | print " </menu>" |
| 111 | 111 | | def showplaintext(self): |
| 112 | 112 | | for programme, time in self.data: |
| 113 | 113 | | print programme, ' Starts: ', time |
| 115 | 115 | | parser = OptionParser( |
| 116 | 116 | | version=" %prog V0.1 Ben Holroyd", |
| 117 | 117 | | description = "%prog - print tv listings, if called without options will check\nthat the listings are upto date and redownload if needed", |
| 118 | 118 | | usage = "%prog [-tp] [--text] [--pipe] [--help]") |
| 119 | 119 | | parser.add_option("-t", "--text", action="store_true", dest="text", default=False, help="print tvlistings in plain text") |
| 120 | 120 | | parser.add_option("-p", "--pipe", action="store_true", dest="pipe", default=False, help="print listings in xml suitable for obmenu") |
| 121 | 121 | | (options, args) = parser.parse_args() |
| 123 | 123 | | for entry in channellist: #check files are up to date |
| 124 | 124 | | if not os.path.exists(tmpfile % entry): |
| 125 | 125 | | downloader(address,channellistdict[entry],entry).start() |
| 127 | 127 | | if options.text: |
| 128 | 128 | | for entry in channellist: |
| 129 | 129 | | print "\n\033[01;04m%s\033[00;00m" % entry #print bold & underlined |
| 130 | 130 | | printer(entry).showplaintext() |
| 132 | 132 | | elif options.pipe : |
| 133 | 133 | | print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" |
| 134 | 134 | | print "<openbox_pipe_menu>" |
| 135 | 135 | | for entry in channellist: printer(entry).showxmlmenu() |
| 136 | 136 | | print "</openbox_pipe_menu>" |