replace sanitizing rules with gPodder's normalize_feed_url()
[mygpo.git] / mygpo / api / advanced / episode.py
blob27fd9d801c6d529e28587a0216f2df67d7318fe1
2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
18 import time
19 from datetime import datetime
21 import dateutil.parser
23 from django.http import HttpResponseBadRequest, Http404
24 from django.views.decorators.csrf import csrf_exempt
25 from django.views.decorators.cache import never_cache
27 from mygpo.core import models
28 from mygpo.api.httpresponse import JsonResponse
29 from mygpo.api.exceptions import ParameterMissing
30 from mygpo.api.backend import get_device
31 from mygpo.users.models import Chapter
32 from mygpo.utils import parse_time, parse_request_body, normalize_feed_url
33 from mygpo.decorators import allowed_methods
34 from mygpo.api.basic_auth import require_valid_user, check_username
35 from mygpo.db.couchdb.episode import episode_for_podcast_url
36 from mygpo.db.couchdb.episode_state import episode_state_for_user_episode
39 @csrf_exempt
40 @require_valid_user
41 @check_username
42 @never_cache
43 @allowed_methods(['POST', 'GET'])
44 def chapters(request, username):
46 now = datetime.utcnow()
47 now_ = int(time.mktime(now.timetuple()))
49 if request.method == 'POST':
50 req = parse_request_body(request)
52 if not 'podcast' in req:
53 return HttpResponseBadRequest('Podcast URL missing')
55 if not 'episode' in req:
56 return HttpResponseBadRequest('Episode URL missing')
58 podcast_url = req.get('podcast', '')
59 episode_url = req.get('episode', '')
60 update_urls = []
62 # podcast sanitizing
63 s_podcast_url = normalize_feed_url(podcast_url)
64 if s_podcast_url != podcast_url:
65 req['podcast'] = s_podcast_url
66 update_urls.append((podcast_url, s_podcast_url or ''))
68 # episode sanitizing
69 s_episode_url = normalize_feed_url(episode_url, 'episode')
70 if s_episode_url != episode_url:
71 req['episode'] = s_episode_url
72 update_urls.append((episode_url, s_episode_url or ''))
74 if (s_podcast_url != '') and (s_episode_url != ''):
75 try:
76 update_chapters(req, request.user)
77 except ParameterMissing, e:
78 return HttpResponseBadRequest(e)
80 return JsonResponse({
81 'update_url': update_url,
82 'timestamp': now_
85 elif request.method == 'GET':
86 if not 'podcast' in request.GET:
87 return HttpResponseBadRequest('podcast URL missing')
89 if not 'episode' in request.GET:
90 return HttpResponseBadRequest('Episode URL missing')
92 podcast_url = request.GET['podcast']
93 episode_url = request.GET['episode']
95 since_ = request.GET.get('since', None)
96 try:
97 since = datetime.fromtimestamp(float(since_)) if since_ else None
98 except ValueError:
99 return HttpResponseBadRequest('since-value is not a valid timestamp')
101 podcast_url = normalize_feed_url(podcast_url)
102 episode_url = normalize_feed_url(episode_url)
103 episode = episode_for_podcast_url(podcast_url, episode_url)
105 if episode is None:
106 raise Http404
108 e_state = episode_state_for_user_episode(request.user, episode)
110 chapterlist = sorted(e_state.chapters, key=lambda c: c.start)
112 if since:
113 chapterlist = filter(lambda c: c.created >= since, chapters)
115 chapters = []
116 for c in chapterlist:
117 if c.device is not None:
118 device = request.user.get_device(c.device)
119 device_uid = device.uid
120 else:
121 device_uid = None
123 chapters.append({
124 'start': c.start,
125 'end': c.end,
126 'label': c.label,
127 'advertisement': c.advertisement,
128 'timestamp': c.created,
129 'device': device_uid
132 return JsonResponse({
133 'chapters': chapters,
134 'timestamp': now_
138 def update_chapters(req, user):
139 podcast_url = normalize_feed_url(req['podcast'])
140 episode_url = normalize_feed_url(req['episode'])
142 episode = episode_for_podcast_url(podcast_url, episode_url,
143 create=True)
145 e_state = episode_state_for_user_episode(request.user, episode)
147 device = None
148 if 'device' in req:
149 device = get_device(request.user, req['device'],
150 request.META.get('HTTP_USER_AGENT', ''), undelete=True)
152 timestamp = dateutil.parser.parse(req['timestamp']) if 'timestamp' in req else datetime.utcnow()
154 new_chapters = parse_new_chapters(request.user, req.get('chapters_add', []))
155 rem_chapters = parse_rem_chapters(req.get('chapters_remove', []))
157 e_state.update_chapters(new_chapters, rem_chapters)
161 def parse_new_chapters(user, chapters):
162 for c in chapters:
163 if not 'start' in c:
164 raise ParameterMissing('start parameter missing')
165 start = parse_time(c['start'])
167 if not 'end' in c:
168 raise ParameterMissing('end parameter missing')
169 end = parse_time(c['end'])
171 label = c.get('label', '')
172 adv = c.get('advertisement', False)
174 device_uid = c.get('device', None)
175 if device_uid:
176 device_id = get_device(user, device_uid,
177 request.META.get('HTTP_USER_AGENT', ''), undelete=True).id
178 else:
179 device_id = None
181 chapter = Chapter()
182 chapter.device = device_id
183 chapter.created = timestamp
184 chapter.start = start
185 chapter.end = end
186 chapter.label = label
187 chapter.advertisement = adv
189 yield chapter
192 def parse_rem_chapters(chapers):
193 for c in chapters:
194 if not 'start' in c:
195 raise ParameterMissing('start parameter missing')
196 start = parse_time(c['start'])
198 if not 'end' in c:
199 raise ParameterMissing('end parameter missing')
200 end = parse_time(c['end'])
202 yield (start, end)