flexget.plugins.services.myepisodes
Covered: 74 lines
Missed: 85 lines
Skipped 48 lines
Percent: 46 %
  1
import logging
  2
import urllib
  3
import urllib2
  4
import re
  5
import cookielib
  6
from datetime import datetime
  7
from sqlalchemy import Column, Integer, String, DateTime
  8
from flexget import schema
  9
from flexget.plugin import register_plugin, DependencyError, PluginWarning
 12
try:
 13
    from flexget.plugins.api_tvdb import lookup_series
 14
except ImportError:
 15
    raise DependencyError(issued_by='myepisodes', missing='api_tvdb',
 16
                          message='myepisodes requires the `api_tvdb` plugin')
 19
log = logging.getLogger('myepisodes')
 20
Base = schema.versioned_base('myepisodes', 0)
 23
class MyEpisodesInfo(Base):
 24
    __tablename__ = 'myepisodes'
 26
    id = Column(Integer, primary_key=True)
 27
    series_name = Column(String, unique=True) # don't know if unique is correct python syntax for saying there must only be one entry with the same content
 28
    myepisodes_id = Column(Integer, unique=True)
 29
    updated = Column(DateTime)
 31
    def __init__(self, series_name, myepisodes_id):
 32
        self.series_name = series_name
 33
        self.myepisodes_id = myepisodes_id
 34
        self.updated = datetime.now()
 36
    def __repr__(self):
 37
        return '<MyEpisodesInfo(series_name=%s, myepisodes_id=%s)>' % (self.series_name, self.myepisodes_id)
 40
class MyEpisodes(object):
 41
    """
 42
    Marks a series episode as acquired in your myepisodes.com account.
 44
    Simple Example:
 46
    Most shows are recognized automatically from their TVDBname. 
 47
    And of course the plugin needs to know your MyEpisodes.com account details.
 49
    feeds:
 50
      tvshows:
 51
        myepisodes:
 52
          username: <username>
 53
          password: <password>
 54
        series:
 55
         - human target
 56
         - chuck
 58
    Advanced Example:
 60
    In some cases, the TVDB name is either not unique or won't even be discovered. 
 61
    In that case you need to specify the MyEpisodes id manually using the set plugin.
 63
    feeds:
 64
      tvshows:
 65
        myepisodes:
 66
          username: <username>
 67
          password: <password>
 68
        series:
 69
         - human target:
 70
             set:
 71
               myepisodes_id: 5111
 72
         - chuck
 74
    How to find the MyEpisodes id: http://matrixagents.org/screencasts/myep_example-20110507-131555.png
 75
    """
 77
    def validator(self):
 78
        from flexget import validator
 79
        root = validator.factory('dict')
 80
        root.accept('text', key='username', required=True)
 81
        root.accept('text', key='password', required=True)
 82
        return root
 84
    def on_feed_exit(self, feed, config):
 85
        """Mark all accepted episodes as acquired on MyEpisodes"""
 86
        if not feed.accepted:
 88
            return
 90
        username = config['username']
 91
        password = config['password']
 93
        cookiejar = cookielib.CookieJar()
 94
        opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar))
 95
        baseurl = urllib2.Request('http://myepisodes.com/login.php?')
 96
        loginparams = urllib.urlencode({'username': username,
 97
                                        'password': password,
 98
                                        'action': 'Login'})
 99
        try:
100
            logincon = opener.open(baseurl, loginparams)
101
            loginsrc = logincon.read()
102
        except urllib2.URLError, e:
103
            log.error('Error logging in to myepisodes: %s' % e)
104
            return
106
        if str(username) not in loginsrc:
107
            raise PluginWarning(('Login to myepisodes.com failed, please check '
108
                                 'your account data or see if the site is down.'), log)
110
        for entry in feed.accepted:
111
            try:
112
                self.mark_episode(feed, entry, opener)
113
            except PluginWarning, w:
114
                log.warning(str(w))
116
    def lookup_myepisodes_id(self, entry, opener, session):
117
        """Populates myepisodes_id field for an entry, and returns the id.
119
        Call will also set entry field `myepisode_id` if successful.
121
        Return:
122
            myepisode id
124
        Raises:
125
            LookupError if entry does not have field series_name
126
        """
129
        if entry.get('myepisodes_id'):
130
            return entry['myepisodes_id']
132
        if not entry.get('series_name'):
133
            raise LookupError('Cannot lookup myepisodes id for entries without series_name')
134
        series_name = entry['series_name']
137
        myepisodes_info = session.query(MyEpisodesInfo).filter(MyEpisodesInfo.series_name == series_name.lower()).first()
138
        if myepisodes_info:
139
            entry['myepisodes_id'] = myepisodes_info.myepisodes_id
140
            return myepisodes_info.myepisodes_id
143
        if entry.get('series_name_tvdb'):
144
            query_name = entry['series_name_tvdb']
145
        else:
146
            try:
147
                series = lookup_series(name=series_name, tvdb_id=entry.get('thetvdb_id'))
148
                query_name = series.seriesname
149
            except LookupError, e:
150
                log.warning('Unable to lookup series `%s` from tvdb, using raw name.' % series_name)
151
                query_name = series_name
153
        baseurl = urllib2.Request('http://myepisodes.com/search.php?')
154
        params = urllib.urlencode({'tvshow': query_name, 'action': 'Search myepisodes.com'})
155
        try:
156
            con = opener.open(baseurl, params)
157
            txt = con.read()
158
        except urllib2.URLError, e:
159
            log.error('Error searching for myepisodes id: %s' % e)
161
        matchObj = re.search(r'&showid=([0-9]*)">' + query_name + '</a>', txt, re.MULTILINE | re.IGNORECASE)
162
        if matchObj:
163
            myepisodes_id = matchObj.group(1)
164
            db_item = session.query(MyEpisodesInfo).filter(MyEpisodesInfo.myepisodes_id == myepisodes_id).first()
165
            if db_item:
166
                log.info('Changing name to `%s` for series with myepisodes_id %s' % 
167
                    (series_name.lower(), myepisodes_id))
168
                db_item.series_name = series_name.lower()
169
            else:
170
                session.add(MyEpisodesInfo(series_name.lower(), myepisodes_id))
171
            entry['myepisodes_id'] = myepisodes_id
172
            return myepisodes_id
174
    def mark_episode(self, feed, entry, opener):
175
        """Mark episode as acquired.
177
        Required entry fields:
178
            - series_name
179
            - series_season
180
            - series_episode
182
        Raises:
183
            PluginWarning if operation fails
184
        """    
186
        if 'series_season' not in entry or 'series_episode' not in entry or 'series_name' not in entry:
187
            raise PluginWarning('Can\'t mark entry `%s` in myepisodes without series_season, series_episode and series_name fields' %
188
                entry['title'], log)
190
        if not self.lookup_myepisodes_id(entry, opener, session=feed.session):
191
            raise PluginWarning('Couldn\'t get myepisodes id for `%s`' % entry['title'], log)
193
        myepisodes_id = entry['myepisodes_id']
194
        season = entry['series_season']
195
        episode = entry['series_episode']
197
        if feed.manager.options.test:
198
            log.info('Would mark %s of `%s` as acquired.' % (entry['series_id'], entry['series_name']))
199
        else:
200
            baseurl2 = urllib2.Request('http://myepisodes.com/myshows.php?action=Update&showid=%s&season=%s&episode=%s&seen=0' % 
201
                (myepisodes_id, season, episode))
202
            opener.open(baseurl2)
203
            log.info('Marked %s of `%s` as acquired.' % (entry['series_id'], entry['series_name']))
206
register_plugin(MyEpisodes, 'myepisodes', api_ver=2)