Hemann commited on
Commit
6e8185c
·
verified ·
1 Parent(s): c75a2d1

Upload 4 files

Browse files
Files changed (4) hide show
  1. app.py +1403 -0
  2. listened_episodes.txt +139 -0
  3. playlists.txt +56 -0
  4. watched_videos.txt +279 -0
app.py ADDED
@@ -0,0 +1,1403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, redirect, url_for, jsonify
2
+ from flask_caching import Cache
3
+ import aiohttp
4
+ import asyncio
5
+ from bs4 import BeautifulSoup, NavigableString
6
+ from playwright.async_api import async_playwright
7
+ from datetime import datetime, timedelta, timezone
8
+ import os
9
+ import praw
10
+ import re
11
+ from playwright_stealth import stealth_async
12
+
13
+ app = Flask(__name__)
14
+ cache = Cache(app, config={'CACHE_TYPE': 'simple'})
15
+
16
+ API_KEY = 'AIzaSyBg3-XPBEXj9Erk1Zc-YDAFTkK9yIx-1BA'
17
+ YOUTUBE_API_URL = 'https://www.googleapis.com/youtube/v3/playlistItems'
18
+ PLAYLIST_API_URL = 'https://www.googleapis.com/youtube/v3/playlists'
19
+ WATCHED_VIDEOS_FILE = 'watched_videos.txt'
20
+ PLAYLISTS_FILE = 'playlists.txt'
21
+ LISTENED_EPISODES_FILE = 'listened_episodes.txt'
22
+
23
+
24
+ async def fetch_videos_from_playlist(session, playlist_id):
25
+ params = {
26
+ 'part': 'snippet',
27
+ 'playlistId': playlist_id,
28
+ 'maxResults': 50,
29
+ 'key': API_KEY
30
+ }
31
+ try:
32
+ async with session.get(YOUTUBE_API_URL, params=params) as response:
33
+ if response.status != 200:
34
+ print(f"Failed to fetch data for playlist {playlist_id}: {response.status} {await response.text()}")
35
+ return []
36
+ data = await response.json()
37
+ return data.get('items', [])
38
+ except Exception as e:
39
+ print(f"Error in fetch_videos_from_playlist for {playlist_id}: {e}")
40
+ return []
41
+
42
+
43
+ async def fetch_playlist_title(session, playlist_id):
44
+ params = {
45
+ 'part': 'snippet',
46
+ 'id': playlist_id,
47
+ 'key': API_KEY,
48
+ }
49
+ try:
50
+ async with session.get(PLAYLIST_API_URL, params=params) as response:
51
+ if response.status == 200:
52
+ data = await response.json()
53
+ if data.get('items'):
54
+ return {
55
+ 'id': data['items'][0]['id'],
56
+ 'title': data['items'][0]['snippet']['title'],
57
+ 'channel_title': data['items'][0]['snippet']['channelTitle']
58
+ }
59
+ else:
60
+ print(f"Failed to fetch playlist title for {playlist_id}: {response.status} {await response.text()}")
61
+ except Exception as e:
62
+ print(f"Error in fetch_playlist_title for {playlist_id}: {e}")
63
+ return None
64
+
65
+ REDDIT_CLIENT_ID = 't8iBT4qixHudVJrC62J_zw'
66
+ REDDIT_CLIENT_SECRET = 'GilsWCFm4d6MiGevxO1YJEiB8SPDPA'
67
+ REDDIT_USER_AGENT = 'upsc_tracker by /u/YOUR_REDDIT_USERNAME'
68
+
69
+ # Initialize PRAW
70
+ reddit = praw.Reddit(
71
+ client_id=REDDIT_CLIENT_ID,
72
+ client_secret=REDDIT_CLIENT_SECRET,
73
+ user_agent=REDDIT_USER_AGENT
74
+ )
75
+
76
+
77
+ SUBREDDITS = ['UPSC','SideProject', 'datascience','explainlikeimfive','Krishnamurti','ycombinator','OpenAI','programming','AskReddit', 'worldnews', 'politics']
78
+
79
+ @app.route('/get_posts', methods=['POST'])
80
+ def get_posts():
81
+ data = request.json
82
+ selected_subreddits = data.get('subreddits', [])
83
+ sort_method = data.get('sort', 'hot')
84
+ limit = int(data.get('limit', 10))
85
+
86
+ all_posts = []
87
+ for subreddit_name in selected_subreddits:
88
+ try:
89
+ subreddit = reddit.subreddit(subreddit_name)
90
+ if sort_method == 'top':
91
+ posts_iterable = subreddit.top(limit=limit)
92
+ elif sort_method == 'new':
93
+ posts_iterable = subreddit.new(limit=limit)
94
+ else:
95
+ posts_iterable = subreddit.hot(limit=limit)
96
+
97
+ for post in posts_iterable:
98
+ all_posts.append({
99
+ 'subreddit': subreddit_name,
100
+ 'title': post.title,
101
+ 'url': post.url,
102
+ 'author': post.author.name if post.author else '[deleted]',
103
+ 'score': post.score,
104
+ 'num_comments': post.num_comments,
105
+ 'created_utc': post.created_utc,
106
+ 'selftext': post.selftext[:300] + '...' if len(post.selftext) > 300 else post.selftext,
107
+ 'link': f"https://reddit.com{post.permalink}",
108
+ 'flair': post.link_flair_text if post.link_flair_text else 'No flair',
109
+ 'nsfw': post.over_18,
110
+ 'spoiler': post.spoiler
111
+ })
112
+ except Exception as e:
113
+ print(f"Error fetching posts from subreddit {subreddit_name}: {e}")
114
+
115
+ return jsonify(all_posts)
116
+
117
+ def filter_videos_by_date(videos, days=5):
118
+ recent_videos = []
119
+ if not videos: return []
120
+ cutoff_date = datetime.now(timezone.utc) - timedelta(days=days)
121
+ for video in videos:
122
+ try:
123
+ publish_date = datetime.strptime(video['snippet']['publishedAt'], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
124
+ if publish_date >= cutoff_date:
125
+ recent_videos.append({
126
+ 'videoId': video['snippet']['resourceId']['videoId'],
127
+ 'title': video['snippet']['title'],
128
+ 'url': f"https://www.youtube.com/watch?v={video['snippet']['resourceId']['videoId']}",
129
+ 'publishedAt': publish_date
130
+ })
131
+ except (KeyError, TypeError, ValueError) as e:
132
+ print(f"Error processing video data: {video.get('snippet', {}).get('title', 'Unknown Video')}. Error: {e}")
133
+ continue
134
+ return recent_videos
135
+
136
+ def load_watched_videos():
137
+ if os.path.exists(WATCHED_VIDEOS_FILE):
138
+ with open(WATCHED_VIDEOS_FILE, 'r') as file:
139
+ return {line.strip() for line in file}
140
+ return set()
141
+
142
+ def save_watched_videos(video_ids):
143
+ try:
144
+ with open(WATCHED_VIDEOS_FILE, 'a') as file:
145
+ for video_id in video_ids:
146
+ file.write(f"{video_id}\n")
147
+ except IOError as e:
148
+ print(f"Error saving watched videos: {e}")
149
+
150
+
151
+ def filter_unseen_videos(videos):
152
+ if not videos: return []
153
+ watched_videos = load_watched_videos()
154
+ return [video for video in videos if video['videoId'] not in watched_videos]
155
+
156
+ async def load_playlists(session):
157
+ playlists_data = []
158
+ if os.path.exists(PLAYLISTS_FILE):
159
+ try:
160
+ with open(PLAYLISTS_FILE, 'r') as file:
161
+ playlist_ids = [line.strip() for line in file]
162
+
163
+ tasks = [fetch_playlist_title(session, playlist_id) for playlist_id in playlist_ids]
164
+ playlist_details_results = await asyncio.gather(*tasks, return_exceptions=True)
165
+
166
+ for detail in playlist_details_results:
167
+ if isinstance(detail, Exception):
168
+ print(f"Error fetching a playlist detail: {detail}")
169
+ elif detail:
170
+ playlists_data.append(detail)
171
+ except Exception as e:
172
+ print(f"Error loading playlists from file or fetching details: {e}")
173
+ return playlists_data
174
+
175
+
176
+ def add_playlist(playlist_id):
177
+ try:
178
+ with open(PLAYLISTS_FILE, 'a') as file:
179
+ file.write(f"{playlist_id}\n")
180
+ except IOError as e:
181
+ print(f"Error adding playlist to file: {e}")
182
+
183
+ async def scrape_air_content(url, title_filter):
184
+ episodes = []
185
+ try:
186
+ async with aiohttp.ClientSession() as session:
187
+ async with session.get(url) as response:
188
+ if response.status != 200:
189
+ print(f"Failed to fetch data from {url}: {response.status}")
190
+ return []
191
+ content = await response.text()
192
+ soup = BeautifulSoup(content, "html.parser")
193
+
194
+ table = soup.find('table', class_='table')
195
+ if not table:
196
+ print(f"Table not found on {url}")
197
+ return []
198
+
199
+ rows = table.find_all('tr')[1:]
200
+ for row in rows:
201
+ cols = row.find_all('td')
202
+ if len(cols) < 4:
203
+ continue
204
+
205
+ title = cols[0].text.strip()
206
+ date_str = cols[1].text.strip()
207
+ time_str = cols[2].text.strip()
208
+
209
+ audio_tag = cols[3].find('audio')
210
+ audio_src = None
211
+ if audio_tag:
212
+ source_tag = audio_tag.find('source')
213
+ if source_tag and 'src' in source_tag.attrs:
214
+ audio_src = source_tag['src']
215
+
216
+ if not audio_src:
217
+ continue
218
+
219
+ try:
220
+ date_obj = datetime.strptime(f"{date_str} {time_str}", '%d %b %Y %H:%M')
221
+ except ValueError:
222
+ print(f"Could not parse date for AIR episode: {date_str} {time_str}")
223
+ continue
224
+
225
+ if title in title_filter:
226
+ episodes.append({
227
+ 'title': title,
228
+ 'date': date_obj,
229
+ 'audio_link': audio_src
230
+ })
231
+ except Exception as e:
232
+ print(f"Error scraping AIR content from {url} for titles {title_filter}: {e}")
233
+ return episodes
234
+
235
+ async def scrape_air_spotlight():
236
+ return await scrape_air_content("https://www.newsonair.gov.in/listen-broadcast-category/daily-broadcast/", ["Spotlight"])
237
+
238
+ async def scrape_air_insight():
239
+ return await scrape_air_content("https://www.newsonair.gov.in/listen-broadcast-category/weekly-broadcast/", ["Insight", "Insights"])
240
+
241
+ async def scrape_air_economy():
242
+ return await scrape_air_content("https://www.newsonair.gov.in/listen-broadcast-category/weekly-broadcast/", ["Money Talk"])
243
+
244
+ async def scrape_current_affairs_air():
245
+ return await scrape_air_content("https://www.newsonair.gov.in/listen-broadcast-category/weekly-broadcast/", ["Current Affairs"])
246
+
247
+ def filter_recent_episodes(episodes, days=3):
248
+ if not episodes: return []
249
+ cutoff_date = datetime.now() - timedelta(days=days)
250
+ return [episode for episode in episodes if episode['date'] >= cutoff_date]
251
+
252
+
253
+ def load_listened_episodes():
254
+ if os.path.exists(LISTENED_EPISODES_FILE):
255
+ with open(LISTENED_EPISODES_FILE, 'r') as file:
256
+ return {line.strip() for line in file}
257
+ return set()
258
+
259
+ def save_listened_episodes(episode_links):
260
+ try:
261
+ with open(LISTENED_EPISODES_FILE, 'a') as file:
262
+ for link in episode_links:
263
+ file.write(f"{link}\n")
264
+ except IOError as e:
265
+ print(f"Error saving listened episodes: {e}")
266
+
267
+
268
+ def filter_unheard_episodes(episodes):
269
+ if not episodes: return []
270
+ listened_episodes = load_listened_episodes()
271
+ return [episode for episode in episodes if episode.get('audio_link') not in listened_episodes]
272
+
273
+
274
+ async def scrape_pib_asp_net(url, ministry=None, year=None, month=None, day=None):
275
+ results = []
276
+ try:
277
+ headers = {
278
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
279
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
280
+ 'Content-Type': 'application/x-www-form-urlencoded',
281
+ 'Origin': 'https://www.pib.gov.in',
282
+ 'Referer': url
283
+ }
284
+
285
+ # Use a session to persist cookies (ASP.NET_SessionId)
286
+ async with aiohttp.ClientSession(headers=headers) as session:
287
+ # 1. GET the page to get ViewState and Cookies
288
+ async with session.get(url, ssl=False, timeout=30) as response:
289
+ if response.status != 200:
290
+ return []
291
+ html = await response.text()
292
+ soup = BeautifulSoup(html, "html.parser")
293
+
294
+ # 2. Extract ALL input values (hidden & visible) to mimic browser state perfectly
295
+ form_data = {}
296
+ for input_tag in soup.find_all('input'):
297
+ if input_tag.get('name'):
298
+ form_data[input_tag.get('name')] = input_tag.get('value', '')
299
+
300
+ # 3. Update with user filters
301
+ # Ensure we don't send 'None', default to '0' (All) or existing defaults
302
+ ministry_val = ministry if ministry and ministry != '0' else '0'
303
+ year_val = year if year else '2024'
304
+ month_val = month if month else '0'
305
+ day_val = day if day else '0'
306
+
307
+ # ASP.NET specific triggers.
308
+ # We trigger the Year dropdown to force a refresh of the list
309
+ overrides = {
310
+ '__EVENTTARGET': 'ctl00$ContentPlaceHolder1$ddlYear',
311
+ '__EVENTARGUMENT': '',
312
+ 'ctl00$ContentPlaceHolder1$ddlMinistry': ministry_val,
313
+ 'ctl00$ContentPlaceHolder1$ddlYear': year_val,
314
+ 'ctl00$ContentPlaceHolder1$ddlMonth': month_val,
315
+ 'ctl00$ContentPlaceHolder1$ddlday': day_val,
316
+ 'ctl00$ContentPlaceHolder1$ddlSector': '0', # Default to All Sector
317
+ }
318
+ form_data.update(overrides)
319
+
320
+ # 4. POST the updated data
321
+ async with session.post(url, data=form_data, ssl=False, timeout=30) as post_response:
322
+ if post_response.status != 200:
323
+ return []
324
+ post_content = await post_response.text()
325
+ post_soup = BeautifulSoup(post_content, "html.parser")
326
+
327
+ # 5. Parse results
328
+ content_area = post_soup.find('div', class_='content-area')
329
+ if content_area:
330
+ for li in content_area.find_all('li'):
331
+ link_tag = li.find('a')
332
+ date_span = li.find('span', class_='publishdatesmall')
333
+
334
+ if link_tag and date_span:
335
+ href = link_tag.get('href', '')
336
+ # Fix relative URLs
337
+ if href and not href.startswith('http'):
338
+ if href.startswith('/'):
339
+ href = f"https://www.pib.gov.in{href}"
340
+ else:
341
+ href = f"https://www.pib.gov.in/{href}"
342
+
343
+ results.append({
344
+ 'title': link_tag.text.strip(),
345
+ 'url': href,
346
+ 'date': date_span.text.replace('Posted on:', '').strip()
347
+ })
348
+
349
+ except Exception as e:
350
+ print(f"PIB ASP scraping error: {e}")
351
+
352
+ return results
353
+
354
+ async def scrape_pib(ministry=None, year=None, month=None, day=None):
355
+ # Backgrounders URL
356
+ url = "https://www.pib.gov.in/ViewBackgrounder.aspx?MenuId=51&reg=3&lang=1"
357
+ data = await scrape_pib_asp_net(url, ministry, year, month, day)
358
+ if data ==[]:
359
+ url1 = "https://www.pib.gov.in/ViewBackgrounder.aspx?MenuId=51"
360
+ data = await scrape_pib_asp_net(url1, ministry, year, month, day)
361
+ return {'Backgrounders': data}
362
+
363
+ async def scrape_pib_facts(ministry=None, year=None, month=None, day=None):
364
+ # Factsheets URL
365
+ url = "https://www.pib.gov.in/AllFactsheet.aspx?MenuId=12&reg=3&lang=1"
366
+ data = await scrape_pib_asp_net(url, ministry, year, month, day)
367
+ if data ==[]:
368
+ url1 = "https://www.pib.gov.in/ViewBackgrounder.aspx?MenuId=51"
369
+ data = await scrape_pib_asp_net(url1, ministry, year, month, day)
370
+
371
+ return {'Backgrounders': data}
372
+
373
+
374
+
375
+ #async def scrape_pib_facts(ministry=None, year=None, month=None, day=None):
376
+ # return await _scrape_pib_with_playwright(
377
+ # "https://pib.gov.in/AllFactsheet.aspx?MenuId=12=3&lang=1",
378
+ # ministry, year, month, day
379
+ # )
380
+
381
+ #async def scrape_pib(ministry=None, year=None, month=None, day=None):
382
+ # return await _scrape_pib_with_playwright(
383
+ # "https://www.pib.gov.in/ViewBackgrounder.aspx?MenuId=51&reg=3&lang=1",
384
+ # ministry, year, month, day
385
+ # )
386
+
387
+
388
+ TH_url = "https://learningcorner.epaper.thehindu.com/articles"
389
+
390
+
391
+ @app.route('/')
392
+ @cache.cached(timeout=300)
393
+ async def index():
394
+ playlist_data = []
395
+ spotlight_episodes, insight_episodes, economy_episodes, current_episodes_air = [], [], [], []
396
+ indian_express_articles_data, orf_articles_data, sansad_tv_summaries_data = [], [], []
397
+ pib_backgrounders_result, pib_facts_result, forum_ca_result = {}, {}, []
398
+ headers = {
399
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
400
+ }
401
+
402
+ async with aiohttp.ClientSession(headers=headers) as session:
403
+ try:
404
+ loaded_playlists = await load_playlists(session)
405
+ for playlist_item in loaded_playlists:
406
+ try:
407
+ videos = await fetch_videos_from_playlist(session, playlist_item['id'])
408
+ recent_videos = filter_videos_by_date(videos, days=5)
409
+ unseen_videos = filter_unseen_videos(recent_videos)
410
+ playlist_data.append({
411
+ 'id': playlist_item['id'],
412
+ 'title': playlist_item['title'],
413
+ 'unseen_count': len(unseen_videos),
414
+ 'channel': playlist_item['channel_title']
415
+ })
416
+ except Exception as e:
417
+ print(f"Error processing playlist {playlist_item.get('id', 'N/A')}: {e}")
418
+ playlist_data.sort(key=lambda x: x['unseen_count'], reverse=True)
419
+ except Exception as e:
420
+ print(f"Error loading or processing playlists: {e}")
421
+
422
+ tasks_to_run = [
423
+ scrape_air_spotlight(),
424
+ scrape_air_insight(),
425
+ scrape_air_economy(),
426
+ scrape_current_affairs_air(),
427
+ scrape_indian_express_articles(),
428
+ scrape_orf_articles(),
429
+ scrape_AIR_sansad_tv_summaries_Iasgyan(),
430
+ scrape_pib(),
431
+ scrape_pib_facts(),
432
+ scrape_forumias(),
433
+ scrape_insights_articles()
434
+ ]
435
+ results = await asyncio.gather(*tasks_to_run, return_exceptions=True)
436
+
437
+ if not isinstance(results[0], Exception): spotlight_episodes = results[0]
438
+ else: print(f"Error scraping AIR Spotlight: {results[0]}")
439
+
440
+ if not isinstance(results[1], Exception): insight_episodes = results[1]
441
+ else: print(f"Error scraping AIR Insight: {results[1]}")
442
+
443
+ if not isinstance(results[2], Exception): economy_episodes = results[2]
444
+ else: print(f"Error scraping AIR Economy: {results[2]}")
445
+
446
+ if not isinstance(results[3], Exception): current_episodes_air = results[3]
447
+ else: print(f"Error scraping AIR Current Affairs: {results[3]}")
448
+
449
+ if not isinstance(results[4], Exception): indian_express_articles_data = results[4]
450
+ else: print(f"Error scraping Indian Express: {results[4]}")
451
+
452
+ if not isinstance(results[5], Exception): orf_articles_data = results[5]
453
+ else: print(f"Error scraping ORF articles: {results[5]}")
454
+
455
+ if not isinstance(results[6], Exception): sansad_tv_summaries_data = results[6]
456
+ else: print(f"Error scraping Sansad TV Summaries (IASGyan): {results[6]}")
457
+
458
+ if not isinstance(results[7], Exception): pib_backgrounders_result = results[7]
459
+ else: print(f"Error scraping PIB Backgrounders: {results[7]}")
460
+
461
+ if not isinstance(results[8], Exception): pib_facts_result = results[8]
462
+ else: print(f"Error scraping PIB Facts: {results[8]}")
463
+
464
+ if not isinstance(results[9], Exception): forum_ca_result = results[9]
465
+ else: print(f"Error scraping ForumIAS CA: {results[9]}")
466
+ if not isinstance(results[9], Exception): insights_articles_result = results[10]
467
+ else: print(f"Error scraping ForumIAS CA: {results[10]}")
468
+
469
+ spotlight_unheard = filter_unheard_episodes(filter_recent_episodes(spotlight_episodes or [], days=5))
470
+ insight_unheard = filter_unheard_episodes(filter_recent_episodes(insight_episodes or [], days=5))
471
+ economy_unheard = filter_unheard_episodes(filter_recent_episodes(economy_episodes or [], days=5))
472
+ current_unheard_air = filter_unheard_episodes(filter_recent_episodes(current_episodes_air or [], days=10))
473
+
474
+
475
+ return render_template('index.html',
476
+ playlists=playlist_data,
477
+ spotlight_unheard_count=len(spotlight_unheard),
478
+ insight_unheard_count=len(insight_unheard),
479
+ economy_unheard_count=len(economy_unheard),
480
+ current_unheard_count=len(current_unheard_air),
481
+ sansad_tv_summaries=sansad_tv_summaries_data or [],
482
+ pib_backgrounders=pib_backgrounders_result or {},
483
+ subreddits=SUBREDDITS,
484
+ articles=indian_express_articles_data or [],
485
+ orfarticles=orf_articles_data or [],
486
+ forum_ca=forum_ca_result or [],
487
+ pibfacts=pib_facts_result or {},
488
+ insightarticles=insights_articles_result or []
489
+ )
490
+
491
+ @app.route('/unseen_videos/<playlist_id>')
492
+ async def unseen_videos(playlist_id):
493
+ unseen_vids = []
494
+ try:
495
+ async with aiohttp.ClientSession() as session:
496
+ videos = await fetch_videos_from_playlist(session, playlist_id)
497
+ recent_videos = filter_videos_by_date(videos, days=5)
498
+ unseen_vids = filter_unseen_videos(recent_videos)
499
+ except Exception as e:
500
+ print(f"Error in /unseen_videos/{playlist_id}: {e}")
501
+ return render_template('unseen_videos.html', videos=unseen_vids, playlist_id=playlist_id)
502
+
503
+ @app.route('/pib')
504
+ async def pibscrap():
505
+ ministry = request.args.get('ministry','0')
506
+ year = request.args.get('year','2024')
507
+ month = request.args.get('month','0')
508
+ day = request.args.get('day','0')
509
+
510
+ pib_data = await scrape_pib(ministry=ministry, year=year, month=month, day=day)
511
+ return render_template('pib.html', pib_backgrounders=pib_data or {})
512
+
513
+ @app.route('/pib_facts')
514
+ async def pibscrapfacts():
515
+ ministry = request.args.get('ministry', '0')
516
+ year = request.args.get('year', '2024')
517
+ month = request.args.get('month', '0')
518
+ day = request.args.get('day', '0')
519
+
520
+ pib_data = await scrape_pib_facts(ministry=ministry, year=year, month=month, day=day)
521
+ return render_template('pib_facts.html', pib_backgrounders=pib_data or {})
522
+
523
+ @app.route('/mark_watched', methods=['POST'])
524
+ def mark_watched():
525
+ video_ids = request.form.getlist('video_ids')
526
+ save_watched_videos(video_ids)
527
+ playlist_id = request.form.get('playlist_id')
528
+ if playlist_id:
529
+ return redirect(url_for('unseen_videos', playlist_id=playlist_id))
530
+ return redirect(url_for('index'))
531
+
532
+
533
+ @app.route('/add_playlist', methods=['GET', 'POST'])
534
+ def add_playlist_route():
535
+ if request.method == 'POST':
536
+ playlist_id = request.form.get('playlist_id', '').strip()
537
+ if playlist_id:
538
+ add_playlist(playlist_id)
539
+ return redirect(url_for('index'))
540
+ return render_template('add_playlist.html')
541
+
542
+ async def _render_air_episodes_page(scrape_function, days_filter, template_name='spotlight.html'):
543
+ episodes_data = []
544
+ try:
545
+ raw_episodes = await scrape_function()
546
+ recent_episodes_data = filter_recent_episodes(raw_episodes or [], days=days_filter)
547
+ episodes_data = filter_unheard_episodes(recent_episodes_data or [])
548
+ except Exception as e:
549
+ print(f"Error in AIR episodes route for {scrape_function.__name__}: {e}")
550
+ return render_template(template_name, episodes=episodes_data)
551
+
552
+ @app.route('/spotlight')
553
+ async def spotlight():
554
+ return await _render_air_episodes_page(scrape_air_spotlight, 5)
555
+
556
+ @app.route('/Insight')
557
+ async def insight():
558
+ return await _render_air_episodes_page(scrape_air_insight, 5)
559
+
560
+ @app.route('/aireconomy')
561
+ async def aireconomy():
562
+ return await _render_air_episodes_page(scrape_air_economy, 5)
563
+
564
+ @app.route('/aircurrentaffairs')
565
+ async def airCA():
566
+ return await _render_air_episodes_page(scrape_current_affairs_air, 10)
567
+
568
+
569
+ @app.route('/mark_listened', methods=['POST'])
570
+ def mark_listened():
571
+ episode_links = request.form.getlist('episode_links')
572
+ save_listened_episodes(episode_links)
573
+ return redirect(request.referrer or url_for('index'))
574
+
575
+
576
+ BASE_URL_MEA = "https://www.mea.gov.in/bilateral-documents.htm"
577
+
578
+ async def fetch_page_mea(session, url): # Renamed
579
+ try:
580
+ async with session.get(url, timeout=20) as response:
581
+ if response.status != 200:
582
+ print(f"Failed to fetch MEA page {url}: {response.status}")
583
+ return None
584
+ return await response.text()
585
+ except Exception as e:
586
+ print(f"Error fetching MEA page {url}: {e}")
587
+ return None
588
+
589
+ async def parse_page_mea(content, days_ago_cutoff_date):
590
+ documents = []
591
+ continue_scraping = True
592
+ if not content:
593
+ return documents, False
594
+
595
+ soup = BeautifulSoup(content, "html.parser")
596
+ item_list = soup.find('ul', class_='commonListing')
597
+
598
+ if item_list:
599
+ for item in item_list.find_all('li'):
600
+ title_link = item.find('a', class_='searchContent')
601
+ date_container = item.find('span', class_='date')
602
+
603
+ if title_link and date_container:
604
+ title = title_link.text.strip()
605
+ doc_url = title_link['href']
606
+ if not doc_url.startswith('http'):
607
+ doc_url = f"https://www.mea.gov.in{doc_url}" if doc_url.startswith('/') else f"https://www.mea.gov.in/{doc_url}"
608
+
609
+ date_str_raw = date_container.text.strip()
610
+
611
+ try:
612
+ date_obj = datetime.strptime(date_str_raw, "%B %d, %Y").replace(tzinfo=timezone.utc)
613
+ if date_obj >= days_ago_cutoff_date:
614
+ documents.append({
615
+ 'title': title,
616
+ 'url': doc_url,
617
+ 'date': date_obj.strftime("%B %d, %Y") # Store as string for template
618
+ })
619
+ else:
620
+ continue_scraping = False # Found an old document, stop
621
+ break # Stop processing this page further
622
+ except ValueError:
623
+ print(f"Error parsing MEA date: {date_str_raw} for title '{title}'")
624
+ else:
625
+ print("MEA commonListing not found.")
626
+ continue_scraping = False # If structure changed, stop.
627
+
628
+ return documents, continue_scraping
629
+
630
+ def get_next_page_url_mea(content):
631
+ if not content: return None
632
+ soup = BeautifulSoup(content, "html.parser")
633
+ next_link = soup.find('a', class_='next')
634
+ if next_link and 'href' in next_link.attrs:
635
+ href = next_link['href']
636
+ if href.startswith('http'):
637
+ return href
638
+ elif href.startswith('/'):
639
+ return f"https://www.mea.gov.in{href}"
640
+ else:
641
+ return f"https://www.mea.gov.in/bilateral-documents/{href}"
642
+ return None
643
+
644
+ async def scrape_bilateral_documents():
645
+ all_documents = []
646
+ try:
647
+ async with aiohttp.ClientSession() as session:
648
+ current_url = f"{BASE_URL_MEA}?53/Bilateral/Multilateral_Documents"
649
+ days_ago_cutoff_date = (datetime.now(timezone.utc) - timedelta(days=90)).replace(hour=0, minute=0, second=0, microsecond=0)
650
+
651
+ page_count = 0
652
+ max_pages = 10
653
+
654
+ while current_url and page_count < max_pages:
655
+ page_count += 1
656
+ print(f"Scraping MEA page {page_count}: {current_url}")
657
+ content = await fetch_page_mea(session, current_url)
658
+ if not content:
659
+ break
660
+
661
+ documents_on_page, continue_scraping = await parse_page_mea(content, days_ago_cutoff_date)
662
+ all_documents.extend(documents_on_page)
663
+
664
+ if not continue_scraping:
665
+ print("Stopping MEA scraping based on date or parsing issue.")
666
+ break
667
+
668
+ current_url = get_next_page_url_mea(content)
669
+ if not current_url:
670
+ print("No next page found for MEA.")
671
+ break
672
+ await asyncio.sleep(1)
673
+ except Exception as e:
674
+ print(f"Error during MEA bilateral documents scraping: {e}")
675
+ return all_documents
676
+
677
+ @app.route('/MEAsite')
678
+ @cache.cached(timeout=3600)
679
+ async def bilateral_documents():
680
+ documents_data = await scrape_bilateral_documents()
681
+ return render_template('bilateral_documents.html', documents=documents_data or [])
682
+
683
+ async def scrape_prs_india():
684
+ cards_data = [] # Renamed
685
+ try:
686
+ url = "https://prsindia.org"
687
+ async with aiohttp.ClientSession() as session:
688
+ async with session.get(url, timeout=20) as response:
689
+ if response.status != 200:
690
+ print(f"Failed to fetch PRS India data: {response.status}")
691
+ return []
692
+ content = await response.text()
693
+ soup = BeautifulSoup(content, "html.parser")
694
+
695
+ right_banner = soup.find('div', class_='right-banner')
696
+ if right_banner:
697
+ for item in right_banner.find_all(['div','section'], class_=re.compile(r"col-\w*-6|card-item-class")):
698
+ image_tag = item.find('img')
699
+ link_tag = item.find('a')
700
+ title_tag = item.find(['h3','h4','h5'])
701
+
702
+ if link_tag and title_tag:
703
+ img_src = None
704
+ if image_tag and 'src' in image_tag.attrs:
705
+ img_src = image_tag['src']
706
+ if not img_src.startswith('http'):
707
+ img_src = url + img_src if img_src.startswith('/') else url + '/' + img_src
708
+
709
+ link_href = link_tag['href']
710
+ if not link_href.startswith('http'):
711
+ link_href = url + link_href if link_href.startswith('/') else url + '/' + link_href
712
+
713
+ cards_data.append({
714
+ 'title': title_tag.text.strip(),
715
+ 'image_url': img_src,
716
+ 'link_url': link_href
717
+ })
718
+ else:
719
+ print("PRS India: right-banner not found or structure changed.")
720
+
721
+ except Exception as e:
722
+ print(f"Error scraping PRS India: {e}")
723
+ return cards_data
724
+
725
+ @app.route('/prsindia')
726
+ @cache.cached(timeout=3600)
727
+ async def prs_india():
728
+ scraped_cards = await scrape_prs_india()
729
+ return render_template('prsindia.html', cards=scraped_cards or [])
730
+
731
+
732
+ async def scrape_current_affairs_iasgyan():
733
+ current_affairs_data = []
734
+ try:
735
+ url = "https://www.iasgyan.in/daily-current-affairs"
736
+ async with aiohttp.ClientSession() as session:
737
+ async with session.get(url, timeout=20) as response:
738
+ if response.status != 200:
739
+ print(f"Failed to fetch IASGyan Current Affairs data: {response.status}")
740
+ return []
741
+ content = await response.text()
742
+ soup = BeautifulSoup(content, "html.parser")
743
+
744
+ cutoff_date_iasgyan = datetime.now() - timedelta(days=6)
745
+
746
+ for article_block in soup.find_all('div', class_='shadow mt-4 rounded-2'):
747
+ title_tag_ias = article_block.find('h3', class_='fw-semibold text-white m-0 fs-5')
748
+ article_links_list = article_block.find_all('a', class_='w-100')
749
+
750
+ if title_tag_ias and article_links_list:
751
+ date_str_match = re.search(r'–\s*(.+)$', title_tag_ias.text.strip())
752
+ if not date_str_match: continue
753
+
754
+ date_str_cleaned = re.sub(r'(\d+)(st|nd|rd|th|TH|ND|RD|ST)\s*', r'\1 ', date_str_match.group(1).strip())
755
+ date_str_cleaned = date_str_cleaned.replace(" ", " ")
756
+
757
+ try:
758
+ article_date_obj = datetime.strptime(date_str_cleaned.strip(), '%d %B %Y')
759
+ except ValueError as ve:
760
+ print(f"IASGyan: Error parsing date '{date_str_cleaned}': {ve}")
761
+ continue
762
+
763
+ if article_date_obj >= cutoff_date_iasgyan:
764
+ articles_for_date = []
765
+ for link_item in article_links_list:
766
+ articles_for_date.append({
767
+ 'title': link_item.text.strip(),
768
+ 'url': link_item['href'] if link_item.get('href') else '#'
769
+ })
770
+ if articles_for_date:
771
+ current_affairs_data.append({
772
+ 'date': article_date_obj,
773
+ 'articles': articles_for_date
774
+ })
775
+ current_affairs_data.sort(key=lambda x: x['date'], reverse=True)
776
+ except Exception as e:
777
+ print(f"Error scraping IASGyan Current Affairs: {e}")
778
+ return current_affairs_data
779
+
780
+
781
+ async def scrape_AIR_sansad_tv_summaries_Iasgyan():
782
+ summaries_data = [] # Renamed
783
+ try:
784
+ url = "https://www.iasgyan.in/sansad-tv-air-summaries"
785
+ async with aiohttp.ClientSession() as session:
786
+ async with session.get(url, timeout=20) as response:
787
+ if response.status != 200:
788
+ print(f"Failed to fetch IASGyan Sansad TV & AIR summaries: {response.status}")
789
+ return []
790
+ content = await response.text()
791
+ soup = BeautifulSoup(content, "html.parser")
792
+
793
+ for summary_block_item in soup.find_all('div', class_='content_bx'): # Renamed
794
+ title_tag_sum = summary_block_item.find('div', class_='title').find('a') if summary_block_item.find('div', class_='title') else None
795
+ date_tag_sum = summary_block_item.find('li', class_='text-muted')
796
+ description_tag_sum = summary_block_item.find('div', class_='short_descr').find('ol') if summary_block_item.find('div', class_='short_descr') else None
797
+ read_more_tag_sum = summary_block_item.find('div', class_='readmore_btn').find('a') if summary_block_item.find('div', class_='readmore_btn') else None
798
+
799
+ if not (title_tag_sum and date_tag_sum and description_tag_sum and read_more_tag_sum):
800
+ continue # Skip if essential elements are missing
801
+
802
+ title_text = title_tag_sum.text.strip()
803
+ doc_url_sum = title_tag_sum['href']
804
+
805
+ # Clean and parse date string, e.g., "25 Sep, 2024" or "25 September, 2024"
806
+ summary_date_str_raw = " ".join(date_tag_sum.text.strip().split()).replace(',', '') # "25 Sep 2024"
807
+ try:
808
+ # Try common formats, this might need adjustment based on actual site variations
809
+ if len(summary_date_str_raw.split()[1]) == 3: # Short month name e.g. Sep
810
+ parsed_date_sum = datetime.strptime(summary_date_str_raw, '%d %b %Y')
811
+ else: # Full month name e.g. September
812
+ parsed_date_sum = datetime.strptime(summary_date_str_raw, '%d %B %Y')
813
+ except ValueError:
814
+ print(f"IASGyan Summaries: Could not parse date '{summary_date_str_raw}' for '{title_text}'")
815
+ parsed_date_sum = datetime.min # Fallback date for sorting if unparsable
816
+
817
+ summary_points_list = [li.text.strip() for li in description_tag_sum.find_all('li')]
818
+
819
+ summaries_data.append({
820
+ 'title': title_text,
821
+ 'url': doc_url_sum,
822
+ 'date_obj': parsed_date_sum, # For sorting
823
+ 'date': parsed_date_sum.strftime('%d %b %Y') if parsed_date_sum != datetime.min else summary_date_str_raw, # Formatted string for display
824
+ 'points': summary_points_list,
825
+ 'read_more_url': read_more_tag_sum['href']
826
+ })
827
+
828
+ summaries_data.sort(key=lambda x: x['date_obj'], reverse=True) # Sort by actual date object
829
+ return summaries_data[:3] # Return top 3 recent
830
+ except Exception as e:
831
+ print(f"Error scraping IASGyan Sansad TV summaries: {e}")
832
+ return summaries_data # Return whatever was collected or empty list
833
+
834
+
835
+ async def scrape_indian_express_articles():
836
+ articles_data = []
837
+ try:
838
+ url = "https://indianexpress.com/section/upsc-current-affairs/upsc-essentials/"
839
+
840
+ headers = {
841
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
842
+ }
843
+
844
+ async with aiohttp.ClientSession(headers=headers) as session:
845
+ async with session.get(url, timeout=20) as response:
846
+ if response.status != 200:
847
+ print(f"Failed to fetch Indian Express UPSC articles: {response.status}")
848
+ return []
849
+ content = await response.text()
850
+ soup = BeautifulSoup(content, "html.parser")
851
+
852
+ one_week_ago_cutoff = datetime.now(timezone.utc) - timedelta(days=7)
853
+
854
+ for article_div in soup.find_all('div', class_='articles'):
855
+ try:
856
+ context_div = article_div.find('div', class_='img-context')
857
+ if not context_div: continue
858
+
859
+ title_tag = context_div.find('h2', class_='title').find('a')
860
+ if not title_tag: continue
861
+
862
+ title_text = title_tag.text.strip()
863
+ doc_url = title_tag['href']
864
+ date_div = context_div.find('div', class_='date')
865
+ date_str_raw = date_div.text.strip() if date_div else ""
866
+ summary_tag = context_div.find('p')
867
+ summary_text = summary_tag.text.strip() if summary_tag else ""
868
+ snaps_div = article_div.find('div', class_='snaps')
869
+ image_url = None
870
+ if snaps_div:
871
+ img = snaps_div.find('img')
872
+ if img:
873
+ image_url = img.get('src') or img.get('data-src')
874
+ article_date_obj = None
875
+ clean_date_str = date_str_raw.replace('IST', '').strip()
876
+ clean_date_str = re.sub(r'\s+', ' ', clean_date_str)
877
+
878
+ try:
879
+ article_date_obj = datetime.strptime(clean_date_str, '%B %d, %Y %H:%M')
880
+ except ValueError:
881
+ try:
882
+ article_date_obj = datetime.strptime(clean_date_str, '%B %d, %Y')
883
+ except ValueError:
884
+ continue
885
+ article_date_obj = article_date_obj.replace(tzinfo=timezone.utc)
886
+
887
+ if article_date_obj >= one_week_ago_cutoff:
888
+ articles_data.append({
889
+ 'title': title_text,
890
+ 'url': doc_url,
891
+ 'image_url': image_url,
892
+ 'date': date_str_raw,
893
+ 'summary': summary_text,
894
+ 'date_obj': article_date_obj
895
+ })
896
+ except Exception as inner_e:
897
+ print(f"Error parsing specific IE article: {inner_e}")
898
+ continue
899
+
900
+ articles_data.sort(key=lambda x: x['date_obj'], reverse=True)
901
+
902
+ except Exception as e:
903
+ print(f"Error scraping Indian Express articles: {e}")
904
+ return articles_data
905
+
906
+
907
+ async def scrape_full_article(url):
908
+ try:
909
+ async with aiohttp.ClientSession() as session:
910
+ async with session.get(url, timeout=20) as response:
911
+ if response.status != 200:
912
+ print(f"Failed to fetch article content from {url}: {response.status}")
913
+ return None
914
+ content = await response.text()
915
+ soup = BeautifulSoup(content, "html.parser")
916
+
917
+ content_div = soup.find('div', id='pcl-full-content') # Highly specific, might break
918
+ if not content_div: # Fallback or broader search
919
+ content_div = soup.find('article') or soup.find('main') or soup.find('div', class_=re.compile(r'content|article-body|story'))
920
+
921
+ if not content_div:
922
+ print(f"Could not find main content container for {url}")
923
+ return f"<p>Content not found. Please visit <a href='{url}'>original article</a>.</p>"
924
+
925
+ title_tag_full = soup.find(['h1', 'h2'], class_=re.compile(r'title|headline')) # Common title classes
926
+ if not title_tag_full: title_tag_full = soup.find('h1') # Generic h1
927
+ title_text_full = title_tag_full.get_text(strip=True) if title_tag_full else "Article"
928
+
929
+ author_date_div = soup.find(['div','span'], class_=re.compile(r'editor|author|date|byline|meta')) # Common meta info classes
930
+ author_date_text_full = author_date_div.get_text(separator=" ", strip=True) if author_date_div else ""
931
+
932
+ # Extract and process paragraphs, headings, lists, blockquotes
933
+ elements = content_div.find_all(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'blockquote', 'figure', 'table'])
934
+ formatted_content_parts = []
935
+ for elem in elements:
936
+ if elem.name in ['h1','h2','h3','h4','h5','h6']:
937
+ formatted_content_parts.append(f"<{elem.name}>{elem.get_text(strip=True)}</{elem.name}>")
938
+ elif elem.name == 'p':
939
+ # Check if paragraph is inside a list item, if so, it's part of the li
940
+ if elem.find_parent('li'):
941
+ continue
942
+ formatted_content_parts.append(f"<p>{elem.get_text(separator=' ', strip=True)}</p>")
943
+ elif elem.name in ['ul', 'ol']:
944
+ list_items_html = "".join([f"<li>{li_item.get_text(separator=' ', strip=True)}</li>" for li_item in elem.find_all('li', recursive=False)])
945
+ formatted_content_parts.append(f"<{elem.name}>{list_items_html}</{elem.name}>")
946
+ elif elem.name == 'blockquote':
947
+ formatted_content_parts.append(f"<blockquote>{elem.get_text(separator=' ', strip=True)}</blockquote>")
948
+ elif elem.name == 'figure':
949
+ img = elem.find('img')
950
+ caption = elem.find('figcaption')
951
+ if img and 'src' in img.attrs:
952
+ img_html = f"<img src='{img['src']}' alt='{img.get('alt','Image')}' style='max-width:100%; height:auto;'>"
953
+ if caption:
954
+ img_html += f"<figcaption>{caption.get_text(strip=True)}</figcaption>"
955
+ formatted_content_parts.append(f"<figure>{img_html}</figure>")
956
+ elif elem.name == 'table':
957
+ # Basic table conversion; for complex tables, more work is needed
958
+ table_html = "<table>"
959
+ for tr in elem.find_all('tr'):
960
+ table_html += "<tr>"
961
+ for th_td in tr.find_all(['th', 'td']):
962
+ table_html += f"<{th_td.name}>{th_td.get_text(strip=True)}</{th_td.name}>"
963
+ table_html += "</tr>"
964
+ table_html += "</table>"
965
+ formatted_content_parts.append(table_html)
966
+
967
+
968
+ full_article_html = f"<h1>{title_text_full}</h1>"
969
+ if author_date_text_full:
970
+ full_article_html += f"<div class='article-meta' style='color:grey; margin-bottom:1em;'>{author_date_text_full}</div>"
971
+ full_article_html += "\n".join(formatted_content_parts)
972
+
973
+ return full_article_html
974
+ except Exception as e:
975
+ print(f"Error scraping full article from {url}: {e}")
976
+ return f"<p>Error loading article content. Please visit <a href='{url}'>original article</a>.</p>"
977
+
978
+
979
+ @app.route('/article/<path:url>')
980
+ async def show_article(url):
981
+ # Ensure the URL is complete if it was passed partially
982
+ if not url.startswith('http'):
983
+ # This is a basic assumption; you might need a more robust way
984
+ # to reconstruct the URL if it's from a specific known domain.
985
+ # For now, we assume it's a full URL passed in the path.
986
+ # If not, this route might need the original domain.
987
+ print(f"Warning: URL '{url}' might be partial. Assuming it's complete.")
988
+
989
+ full_content_html = await scrape_full_article(url) # Renamed
990
+ if full_content_html is None: # Should now return error HTML string
991
+ full_content_html = f"<p>Failed to fetch article content for {url}.</p>"
992
+ return render_template('full_article.html', content=full_content_html)
993
+
994
+
995
+ async def scrape_insights_articles():
996
+ articles_data_insights = []
997
+ try:
998
+ # Updated URL for the main Answer Writing page
999
+ url = 'https://www.insightsonindia.com/upsc-mains-answer-writing-2025-insights-ias/'
1000
+
1001
+ headers = {
1002
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
1003
+ }
1004
+
1005
+ async with aiohttp.ClientSession(headers=headers) as session:
1006
+ async with session.get(url, timeout=20) as response:
1007
+ if response.status != 200:
1008
+ print(f"Failed to fetch Insights Answer Writing links: {response.status}")
1009
+ return []
1010
+ content = await response.text()
1011
+ soup = BeautifulSoup(content, 'html.parser')
1012
+ count = 0
1013
+ for div_block in soup.find_all('div', class_='list_div'):
1014
+ if count >= 2: break
1015
+
1016
+ ul_tag = div_block.find('ul', class_='lcp_catlist')
1017
+ if ul_tag:
1018
+ for li in ul_tag.find_all('li'):
1019
+ a_tag = li.find('a')
1020
+ if a_tag and a_tag.get('href'):
1021
+ title = a_tag.text.strip()
1022
+ link = a_tag['href']
1023
+ articles_data_insights.append({
1024
+ 'title': title,
1025
+ 'link': link
1026
+ })
1027
+ count += 1
1028
+
1029
+ except Exception as e:
1030
+ print(f"Error scraping Insights Answer Writing links: {e}")
1031
+ print(articles_data_insights)
1032
+ return articles_data_insights
1033
+
1034
+ async def scrape_full_article_insight(article_url):
1035
+ filtered_content_parts = [] # Renamed
1036
+ try:
1037
+ async with aiohttp.ClientSession() as session:
1038
+ async with session.get(article_url, timeout=20) as response:
1039
+ if response.status != 200:
1040
+ print(f"Failed to fetch Insights article content from {article_url}: {response.status}")
1041
+ return None # Or some error indicator
1042
+ content = await response.text()
1043
+ soup = BeautifulSoup(content, 'html.parser')
1044
+
1045
+ # The original logic was very specific (GS sections).
1046
+ # A more general approach might be to find the main article body.
1047
+ article_body = soup.find('div', class_=re.compile(r'entry-content|article-content|post-content'))
1048
+ if not article_body:
1049
+ print(f"Insights: Could not find article body for {article_url}")
1050
+ return [{'type': 'p', 'text': 'Article content not found.'}]
1051
+
1052
+ current_section_text = None # Renamed
1053
+
1054
+ for tag_item in article_body.find_all(['h1','h2','h3', 'h4', 'p', 'ul', 'ol', 'blockquote', 'table']): # Added more tags
1055
+ if tag_item.name in ['h1','h2','h3','h4']: # Generic heading
1056
+ current_section_text = tag_item.text.strip()
1057
+ filtered_content_parts.append({'type': tag_item.name, 'text': current_section_text})
1058
+ elif tag_item.name == 'p':
1059
+ # Add paragraph text, optionally under a section if logic requires
1060
+ filtered_content_parts.append({'type': 'p', 'text': tag_item.text.strip()})
1061
+ elif tag_item.name in ['ul', 'ol']:
1062
+ items = [li.get_text(strip=True) for li in tag_item.find_all('li')]
1063
+ if items:
1064
+ filtered_content_parts.append({'type': 'list', 'ordered': tag_item.name == 'ol', 'items': items})
1065
+ elif tag_item.name == 'blockquote':
1066
+ filtered_content_parts.append({'type': 'blockquote', 'text': tag_item.get_text(strip=True)})
1067
+ elif tag_item.name == 'table':
1068
+ rows = []
1069
+ for tr in tag_item.find_all('tr'):
1070
+ cells = [td.get_text(strip=True) for td in tr.find_all(['th', 'td'])]
1071
+ rows.append(cells)
1072
+ if rows:
1073
+ filtered_content_parts.append({'type': 'table', 'rows': rows})
1074
+
1075
+
1076
+ except Exception as e:
1077
+ print(f"Error scraping full Insights article from {article_url}: {e}")
1078
+ return [{'type': 'p', 'text': f'Error loading article: {e}'}]
1079
+ return filtered_content_parts
1080
+
1081
+
1082
+ @app.route('/article_insight/<path:url>')
1083
+ async def show_article_insight(url):
1084
+ full_content_data = await scrape_full_article_insight(url) # Renamed
1085
+ if not full_content_data: # Check if None or empty
1086
+ return "Failed to load the Insights article content.", 404
1087
+ return render_template('full_article_insight.html', content=full_content_data)
1088
+
1089
+
1090
+ async def scrape_orf_articles():
1091
+ orf_articles_data = []
1092
+ try:
1093
+ url = 'https://www.orfonline.org/content-type/issue-briefs'
1094
+
1095
+ headers = {
1096
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
1097
+ }
1098
+
1099
+ async with aiohttp.ClientSession(headers=headers) as session:
1100
+ async with session.get(url, timeout=20) as response:
1101
+ if response.status != 200:
1102
+ print(f"Failed to fetch ORF articles from {url}: {response.status}")
1103
+ return []
1104
+ content = await response.text()
1105
+ soup = BeautifulSoup(content, 'html.parser')
1106
+
1107
+ cutoff_orf = datetime.now(timezone.utc) - timedelta(days=45)
1108
+
1109
+ # Generic search for article cards to handle site changes
1110
+ # We look for common container classes used in grid layouts
1111
+ potential_articles = soup.find_all('div', class_=re.compile(r'col-|card|item|listing|post'))
1112
+
1113
+ for article_block in potential_articles:
1114
+ # 1. Find Title (usually in h2 or h3)
1115
+ title_tag = article_block.find(['h2', 'h3'])
1116
+ if not title_tag:
1117
+ continue
1118
+
1119
+ title_text = title_tag.get_text(strip=True)
1120
+ if not title_text or len(title_text) < 10: # Skip tiny/irrelevant headings
1121
+ continue
1122
+
1123
+ # 2. Find Link (Check inside title, or parent of title, or anywhere in block)
1124
+ link_tag = title_tag.find('a')
1125
+ if not link_tag:
1126
+ # Check if the block itself is wrapped in an 'a' tag
1127
+ link_tag = article_block.find('a')
1128
+
1129
+ if not link_tag or not link_tag.get('href'):
1130
+ continue
1131
+
1132
+ doc_url = link_tag['href']
1133
+ if not doc_url.startswith('http'):
1134
+ doc_url = f"https://www.orfonline.org{doc_url}" if doc_url.startswith('/') else f"https://www.orfonline.org/{doc_url}"
1135
+
1136
+ # 3. Find Date (Look for <time> or elements with 'date' class)
1137
+ date_tag = article_block.find('time') or article_block.find(class_=re.compile(r'date|meta|time'))
1138
+ article_date_obj = None
1139
+
1140
+ if date_tag:
1141
+ date_str = date_tag.get_text(strip=True)
1142
+ try:
1143
+ # Parse "Sep 24, 2024" or "24 September 2024"
1144
+ article_date_obj = datetime.strptime(date_str, "%b %d, %Y").replace(tzinfo=timezone.utc)
1145
+ except ValueError:
1146
+ try:
1147
+ article_date_obj = datetime.strptime(date_str, "%d %B %Y").replace(tzinfo=timezone.utc)
1148
+ except ValueError:
1149
+ pass # Keep None if parsing fails
1150
+
1151
+ # Fallback: If no date found, include it anyway if it looks like an article
1152
+ if not article_date_obj:
1153
+ article_date_obj = datetime.now(timezone.utc) # Default to now if undatable
1154
+
1155
+ if article_date_obj >= cutoff_orf:
1156
+ # 4. Find Description/Author
1157
+ desc_tag = article_block.find('p')
1158
+ desc_text = desc_tag.get_text(strip=True) if desc_tag else ""
1159
+
1160
+ # Prevent duplicates (simple check)
1161
+ if any(a['link'] == doc_url for a in orf_articles_data):
1162
+ continue
1163
+
1164
+ orf_articles_data.append({
1165
+ 'title': title_text,
1166
+ 'link': doc_url,
1167
+ 'date_obj': article_date_obj,
1168
+ 'date': article_date_obj.strftime('%B %d, %Y'),
1169
+ 'description': desc_text,
1170
+ 'author': "ORF" # Author extraction is often messy, defaulting to ORF
1171
+ })
1172
+
1173
+ except Exception as e:
1174
+ print(f"Error scraping ORF articles: {e}")
1175
+
1176
+ # Deduplicate and sort
1177
+ seen = set()
1178
+ unique_data = []
1179
+ for d in orf_articles_data:
1180
+ if d['link'] not in seen:
1181
+ seen.add(d['link'])
1182
+ unique_data.append(d)
1183
+
1184
+ unique_data.sort(key=lambda x: x['date_obj'], reverse=True)
1185
+ return unique_data
1186
+
1187
+
1188
+ async def scrape_forumias():
1189
+ sections_data = []
1190
+ try:
1191
+ # Updated URL based on your input
1192
+ url = "https://forumias.com/blog/7pm/"
1193
+
1194
+ headers = {
1195
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
1196
+ }
1197
+
1198
+ async with aiohttp.ClientSession(headers=headers) as session:
1199
+ async with session.get(url, timeout=20) as response:
1200
+ if response.status != 200:
1201
+ print(f"Failed to fetch ForumIAS CA links: {response.status}")
1202
+ return []
1203
+ content = await response.text()
1204
+ soup = BeautifulSoup(content, "html.parser")
1205
+
1206
+ # The new structure contains monthly tabs. We grab the first active output container.
1207
+ # Usually the first .ajax-cat-archive-output contains the latest loaded month.
1208
+ archive_container = soup.find('div', class_='ajax-cat-archive-output')
1209
+
1210
+ if archive_container:
1211
+ current_section_title = "Latest 7 PM Editorials"
1212
+ articles_in_section = []
1213
+
1214
+ # Iterate through the date groups (e.g., 27 NOV, 26 NOV)
1215
+ for date_group in archive_container.find_all('div', class_='cat-archive-date-group'):
1216
+
1217
+ # Extract Date info (optional, just for context)
1218
+ date_div = date_group.find('div', class_='post-date')
1219
+ date_text = date_div.text.strip() if date_div else ""
1220
+
1221
+ # Find the list of articles for this date
1222
+ ul_list = date_group.find('ul', class_='cat-archive-list')
1223
+ if ul_list:
1224
+ for li in ul_list.find_all('li'):
1225
+ a_tag = li.find('a')
1226
+ if a_tag and a_tag.get('href'):
1227
+ title = a_tag.text.strip()
1228
+ if date_text:
1229
+ title = f"[{date_text}] {title}"
1230
+
1231
+ articles_in_section.append({
1232
+ 'title': title,
1233
+ 'url': a_tag['href']
1234
+ })
1235
+
1236
+ if articles_in_section:
1237
+ sections_data.append({
1238
+ 'section': current_section_title,
1239
+ 'articles': articles_in_section
1240
+ })
1241
+ else:
1242
+ print("ForumIAS: No archive output found.")
1243
+
1244
+ except Exception as e:
1245
+ print(f"Error scraping ForumIAS CA links: {e}")
1246
+ return sections_data
1247
+
1248
+
1249
+ async def scrape_forumias_article(article_url):
1250
+ content_parts_forum = []
1251
+ try:
1252
+ headers = {
1253
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
1254
+ }
1255
+
1256
+ async with aiohttp.ClientSession(headers=headers) as session:
1257
+ async with session.get(article_url, timeout=30) as response:
1258
+ if response.status != 200:
1259
+ print(f"Failed to fetch ForumIAS article content from {article_url}: {response.status}")
1260
+ return [{'type':'error', 'text': f'Failed to fetch: {response.status}'}]
1261
+
1262
+ content = await response.text()
1263
+ soup = BeautifulSoup(content, "html.parser")
1264
+
1265
+ # Main content container
1266
+ article_content_div = soup.find('div', class_='entry-content')
1267
+
1268
+ if not article_content_div:
1269
+ print(f"ForumIAS: Article content div 'entry-content' not found for {article_url}")
1270
+ return [{'type':'error', 'text': 'Main content area not found.'}]
1271
+
1272
+ # Remove ads and clutter before processing
1273
+ for garbage in article_content_div.find_all(['div'], class_=re.compile(r'mobile_ad|web_ad|sharedaddy|robots-nocontent')):
1274
+ garbage.decompose()
1275
+
1276
+ # Process children
1277
+ for element in article_content_div.children:
1278
+ if isinstance(element, NavigableString):
1279
+ text = str(element).strip()
1280
+ if text:
1281
+ content_parts_forum.append({'type': 'text', 'text': text})
1282
+ continue
1283
+
1284
+ if not hasattr(element, 'name') or not element.name:
1285
+ continue
1286
+
1287
+ tag_name = element.name
1288
+ # Get text with separator to handle <br> inside paragraphs nicely
1289
+ text_content = element.get_text(separator=" ", strip=True)
1290
+
1291
+ if tag_name == 'p':
1292
+ # Ignore empty paragraphs
1293
+ if text_content:
1294
+ content_parts_forum.append({'type': 'paragraph', 'text': text_content})
1295
+
1296
+ elif tag_name in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
1297
+ content_parts_forum.append({'type': 'header', 'level': tag_name[1], 'text': text_content})
1298
+
1299
+ elif tag_name in ['ul', 'ol']:
1300
+ list_items = []
1301
+ for li in element.find_all('li', recursive=False):
1302
+ li_text = li.get_text(separator=" ", strip=True)
1303
+ if li_text:
1304
+ list_items.append({'text': li_text})
1305
+ if list_items:
1306
+ content_parts_forum.append({'type': 'list', 'ordered': tag_name == 'ol', 'items': list_items})
1307
+
1308
+ elif tag_name == 'table':
1309
+ # Simple table extraction
1310
+ rows = []
1311
+ for tr in element.find_all('tr'):
1312
+ cells = [td.get_text(separator=" ", strip=True) for td in tr.find_all(['th', 'td'])]
1313
+ rows.append(cells)
1314
+ if rows:
1315
+ content_parts_forum.append({'type': 'table', 'data': rows})
1316
+
1317
+ elif tag_name == 'figure' or (tag_name == 'div' and 'wp-caption' in element.get('class', [])):
1318
+ img = element.find('img')
1319
+ if img and img.get('src'):
1320
+ caption = element.find('figcaption')
1321
+ caption_text = caption.get_text(strip=True) if caption else ''
1322
+ content_parts_forum.append({
1323
+ 'type': 'image',
1324
+ 'src': img['src'],
1325
+ 'alt': img.get('alt', ''),
1326
+ 'caption': caption_text
1327
+ })
1328
+
1329
+ elif tag_name == 'blockquote':
1330
+ content_parts_forum.append({'type': 'blockquote', 'text': text_content})
1331
+
1332
+ except Exception as e:
1333
+ print(f"An error occurred in scrape_forumias_article for {article_url}: {e}")
1334
+ content_parts_forum.append({'type':'error', 'text': f'Error processing article: {e}'})
1335
+
1336
+ return content_parts_forum
1337
+
1338
+
1339
+ @app.route('/forumias')
1340
+ async def forumias():
1341
+ scraped_sections = await scrape_forumias() # Renamed
1342
+ return render_template('forumias.html', sections=scraped_sections or [])
1343
+
1344
+ @app.route('/forumias_article/<path:url>')
1345
+ async def forumias_article(url):
1346
+ # The URL from path might not be complete; ForumIAS URLs are usually absolute from their site
1347
+ # Assuming `url` IS the full URL as per previous structure
1348
+ article_content_data = await scrape_forumias_article(url) # Renamed
1349
+ # `scrape_forumias_article` now returns error dicts if issues, so it won't be None
1350
+ return render_template('forumias_article.html', content=article_content_data)
1351
+
1352
+
1353
+ @app.route('/TH_article/<path:url>') # The Hindu Learning Corner specific article
1354
+ async def show_th_article(url):
1355
+ # This needs a proper `scrape_TH_learning_article(url)` function
1356
+ # The existing `scrape_TH_learning` seems to get a list page.
1357
+ # For now, let's assume `scrape_TH_learning` is meant for a single article URL.
1358
+ article_content_data = await scrape_TH_learning(url) # Renamed
1359
+ if not article_content_data: # Check if None or empty
1360
+ return "Failed to load The Hindu Learning Corner article content.", 404
1361
+ return render_template('article_content.html', content=article_content_data)
1362
+
1363
+ # TH_url = "https://learningcorner.epaper.thehindu.com/articles" # This is a listing page
1364
+ async def scrape_TH_learning(article_url_th): # Renamed param for clarity (single article)
1365
+ article_content_th = [] # Renamed
1366
+ try:
1367
+ # This function should scrape a SINGLE article from TH Learning Corner given its URL
1368
+ async with aiohttp.ClientSession() as session:
1369
+ async with session.get(article_url_th, timeout=20) as response:
1370
+ if response.status != 200:
1371
+ print(f"Failed to fetch TH Learning article from {article_url_th}: {response.status}")
1372
+ return None
1373
+ content = await response.text()
1374
+ soup = BeautifulSoup(content, "html.parser")
1375
+
1376
+ # Find the main content area of the article. This is highly site-specific.
1377
+ # Example: <div class="article-content-id"> or <article>
1378
+ main_content_area = soup.find('div', class_=re.compile(r'articlebody|content|story-body')) # Adjust selector
1379
+ if not main_content_area:
1380
+ main_content_area = soup # Fallback to whole page if specific container not found
1381
+
1382
+ for tag_item_th in main_content_area.find_all(['h1','h2','h3', 'h4', 'p', 'ul', 'ol']): # Renamed
1383
+ if tag_item_th.name in ['h1','h2','h3','h4']:
1384
+ article_content_th.append({'type': tag_item_th.name, 'text': tag_item_th.text.strip()})
1385
+ elif tag_item_th.name == 'p':
1386
+ article_content_th.append({'type': 'p', 'text': tag_item_th.text.strip()})
1387
+ elif tag_item_th.name in ['ul', 'ol']:
1388
+ items = [li.get_text(strip=True) for li in tag_item_th.find_all('li')]
1389
+ if items:
1390
+ article_content_th.append({'type':'list', 'ordered': tag_item_th.name=='ol', 'items':items})
1391
+
1392
+ if not article_content_th and main_content_area == soup: # If fallback used and still no content
1393
+ print(f"TH Learning: No specific content tags found on {article_url_th}, page might be structured differently.")
1394
+ # Could add a generic text extraction as a last resort if needed
1395
+
1396
+ except Exception as e:
1397
+ print(f"Error scraping TH Learning article from {article_url_th}: {e}")
1398
+ return None # Indicate failure
1399
+ return article_content_th
1400
+
1401
+
1402
+ if __name__ == '__main__':
1403
+ app.run(debug=True, host='0.0.0.0', port=int(os.environ.get("PORT", 5000)))
listened_episodes.txt ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/11-09-2024-FINAL-SPOTLIGHT.mp3
2
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/in.mp3
3
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/spotlight111-3.mp3
4
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/spotlight11.mp3
5
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/24-09-2024-SPOTLIGHT-FINAL.mp3
6
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/spotlight11-2.mp3
7
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/spotlight11-1.mp3
8
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/CURRENT-AFFAIRS2130.mp3
9
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/Money-Talk-1.mp3
10
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/25-09-2024-spotligh.mp3
11
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/spotlight111-4.mp3
12
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/spotlight-5.mp3
13
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/insight.mp3
14
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight.mp3
15
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/MONEY-TALK.mp3
16
+ https://www.newsonair.gov.in/wp-content/uploads/2024/09/ca.mp3
17
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight-1.mp3
18
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/sportlight-.mp3
19
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/INSIGHTS.mp3
20
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spo.mp3
21
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/current-affair.mp3
22
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight-2.mp3
23
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotilight.mp3
24
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight-3.mp3
25
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlightfinal.mp3
26
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/SPOTLIGHT.mp3
27
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight2115.mp3
28
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spolight.mp3
29
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/Mony-Talk.mp3
30
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight-7.mp3
31
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/INSIGHTS-1.mp3
32
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/cURRENT-AFFAIRS.mp3
33
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight11.mp3
34
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotligjht1.mp3
35
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight-8.mp3
36
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/SPOTLIGHT-2.mp3
37
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/SPOTLIGHT-1.mp3
38
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight-9.mp3
39
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/INSIGHT.mp3
40
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight-11.mp3
41
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spotlight-10.mp3
42
+ https://www.newsonair.gov.in/wp-content/uploads/2024/10/spot-2.mp3
43
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/spotlighhhht.mp3
44
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/spotlight2115.mp3
45
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/SPOTLGHT.mp3
46
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/spotlighhhhht.mp3
47
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/INSIGHTS.mp3
48
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/MONEY-TALK.mp3
49
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/spotlight-4.mp3
50
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/spotlight-5.mp3
51
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/SPOTLIGHT-FINAL.mp3
52
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/spotlight-6.mp3
53
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/SPOTLIGHT-2.mp3
54
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/INSIGHTS-1.mp3
55
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/29-11-24-FINAL-CURRENT-AFFAIRS.mp3
56
+ https://www.newsonair.gov.in/wp-content/uploads/2024/11/current.mp3
57
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/SPOTLIGHT.mp3
58
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-1.mp3
59
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight.mp3
60
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/SPOTLIGHT.mp3
61
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-1.mp3
62
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight.mp3
63
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/sssssssssssssss.mp3
64
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/2115-spotlight.mp3
65
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/05-12-2024-INSIGHT-FINAL.mp3
66
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-3.mp3
67
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/current.mp3
68
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotliggght.mp3
69
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spot-1.mp3
70
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/MONEYTALK.mp3
71
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spo11lsud-.mp3
72
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlighhhht.mp3
73
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/INSIGHTS.mp3
74
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-4.mp3
75
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/FINAL-CURRENT-AFFAIRS-13-12-2024.mp3
76
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/sp.mp3
77
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlighhht.mp3
78
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spot-2.mp3
79
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-5.mp3
80
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/MONEY-TALK.mp3
81
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spoti-22.mp3
82
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spoti-22.mp3
83
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/current-affairs.mp3
84
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-7.mp3
85
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlighhht-1.mp3
86
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/inghsts-222.mp3
87
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-8.mp3
88
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spot22.mp3
89
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/MONEY-TALK-1.mp3
90
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-9.mp3
91
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlighhht-2.mp3
92
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-10.mp3
93
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/current-1.mp3
94
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/FINAL-26-12-2024-INSIGHT.mp3
95
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-11.mp3
96
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spotlight-12.mp3
97
+ https://www.newsonair.gov.in/wp-content/uploads/2024/12/spp.mp3
98
+ https://www.newsonair.gov.in/wp-content/uploads/2025/01/CURRENT-AFFAIRS.mp3
99
+ https://www.newsonair.gov.in/wp-content/uploads/2025/01/spotlighhht2115.mp3
100
+ https://www.newsonair.gov.in/wp-content/uploads/2025/01/spotlight-1.mp3
101
+ https://www.newsonair.gov.in/wp-content/uploads/2025/01/INSIGHTS.mp3
102
+ https://www.newsonair.gov.in/wp-content/uploads/2025/01/spotlight-3.mp3
103
+ https://www.newsonair.gov.in/wp-content/uploads/2025/01/spotlitht.mp3
104
+ https://www.newsonair.gov.in/wp-content/uploads/2025/01/spotlightt.mp3
105
+ https://www.newsonair.gov.in/wp-content/uploads/2025/01/money-ad111-.mp3
106
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/Spotlight.mp3
107
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/33.mp3
108
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/spotlight-1.mp3
109
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/spppppppppppppppp.mp3
110
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/INSIGHT-FINAL.mp3
111
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/INSIGHT-FINAL.mp3
112
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/current-.mp3
113
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/spotligh2115t.mp3
114
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/spot-1.mp3
115
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/13-03-2025-SPOTLIGHT-FINAL.mp3
116
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/SPOTLIGHT.mp3
117
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/INSIGHT.mp3
118
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/spotlight-2.mp3
119
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/Mony.mp3
120
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/spotlight-3.mp3
121
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/sp.mp3
122
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/INSIGHt.mp3
123
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/INSIGHt.mp3
124
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/spot-338973111.mp3
125
+ https://www.newsonair.gov.in/wp-content/uploads/2025/03/spotlight-6.mp3
126
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/03-04-2025-SPOTLIGHT-FINAL.mp3
127
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/SPOTLIGHT.mp3
128
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/Spot45.mp3
129
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/spotlight-3.mp3
130
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/111.mp3
131
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/spotlight-5.mp3
132
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/spotlight-4.mp3
133
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/SPOTLIGHT-2.mp3
134
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/spotlight-9.mp3
135
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/sp-1.mp3
136
+ https://www.newsonair.gov.in/wp-content/uploads/2025/04/spot.mp3
137
+ https://www.newsonair.gov.in/wp-content/uploads/2025/05/INSIGHT2145.mp3
138
+ https://www.newsonair.gov.in/wp-content/uploads/2025/05/Spot.mp3
139
+ https://www.newsonair.gov.in/wp-content/uploads/2025/05/spotlight-1.mp3
playlists.txt ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ PLVOgwA_DiGzr4qkdqR78JnRCzEuq-jKnC
2
+ PLfa0eRI2uIaR1iOJnq7tklxIJDLxTxI5y
3
+ PLfa0eRI2uIaTKUwBPPnMEIGEriEsw__4I
4
+ PLfa0eRI2uIaRsbmO9h8efyTgt9trCK9HK
5
+ PLfa0eRI2uIaSF0WKEDAEHP024wsf_gLYb
6
+ PL694841F3B0AD5C8E
7
+ PLv-SNV2XmnZmbpJt0kGD1-strASK7rVXO
8
+ PL8dPuuaLjXtMg4YXg6jCIDMbHUDaHDXXZ
9
+ PL8dPuuaLjXtOkcA7TOHY56KsnNZv4gFg5
10
+ PL8dPuuaLjXtMx8ZIQV9NduU_HFjDwykuj
11
+ PL8dPuuaLjXtO85Sl24rSiVQ93q7vcntNF
12
+ PL8dPuuaLjXtNgK6MZucdYldNkMybYIHKR
13
+ PLBDA2E52FB1EF80C9
14
+ PL8dPuuaLjXtNjasccl-WajpONGX3zoY4M
15
+ PL9wl9ZX1TMB4p24hDLVpyRM2XmtDXgjai
16
+ PLVOgwA_DiGzo6lbWoMv5-5NddA-m5KKkV
17
+ PLVOgwA_DiGzrOZ062PskBVE83abtupy3z
18
+ PLcDghvQhYD9LHTquZZsaAsQXge1yZ7GB_
19
+ PLpgVhY9jyWA9Ww9l8qngqPiYFzk0JSvq3
20
+ PLpgVhY9jyWA_6-4kc9pNZBoe2xj2yLUwu
21
+ PLpgVhY9jyWA-1BOprzD-4azQC3uofTK21
22
+ PLpgVhY9jyWA8WccbJScjyNaMc4sCxgnXD
23
+ PLpgVhY9jyWA_DQlPbKMOcdwlfoJVLtEtm
24
+ PLpgVhY9jyWA-FZc0vqGMf1zY3CTacmkhQ
25
+ PLpgVhY9jyWA_3tZv5DSR0Y33miqfj7pS-
26
+ PLpgVhY9jyWA-0Qvqd6XrTTSrvnBeXM3KP
27
+ PLpgVhY9jyWA-nPAESkhKkuIetbNOw0cyM
28
+ PLpgVhY9jyWA_DQlPbKMOcdwlfoJVLtEtm
29
+ PLsHvWYTWngjU10OvNme3xwzAyJAL9kZ2O
30
+ PLsHvWYTWngjXI_-3Z5gRoRWrDqwvep299
31
+ PLsHvWYTWngjVUGe5A617AHv3ipPwHOzI8
32
+ PLsHvWYTWngjWvQqAie7RAEAxBomha79d1
33
+ PLw3TDbfl2YpMSJrPZi0Ipd-WmgcZOVEwu
34
+ PLsHvWYTWngjWfDde4IA0QNxwqGHK19P6u
35
+ PLpgVhY9jyWA9vJPysBTAQofUhrA_uY4A_
36
+ PLpgVhY9jyWA_uNy35gTrwl_OfJzmOVDCa
37
+ PLpgVhY9jyWA-VLEZwMuRs51PKehlB85k9
38
+ PLfa0eRI2uIaT_w4_pYp99-aOuSvYG4hPw
39
+ PLVOgwA_DiGzpAFPXUhGalPSNvmwd8zEzQ
40
+ PL_P3LWRX3ZmoDNH46fq4vGhD7zBsmctRE
41
+ PLfa0eRI2uIaT929e6VZoS0uNkC0Y1Pxf1
42
+ PLmiRlMn-Wmov6Ut9e6gHj2yFNwCVILC-j
43
+ PLmiRlMn-Wmos2zwvRHeeAOAKCWcDrmFBO
44
+ PLmiRlMn-WmovOtAbSdL5mW_wx0BPGQW5D
45
+ PLIG8a9wNRHVu-Aw2VgUJacXlpsJMbF5Y_
46
+ PLzGHKb8i9vTxodrArxXFPziklf0iqFdC5
47
+ PLbTuoCzCZmM9yjE-touBFCaUWOhhzzNRm
48
+ PLQG-bVtyB9_bXcb-tkYGAzk-DY-P1GloP
49
+ PLh6oq2EbSNEd8wPXDYSIILG8NrqPg7U-f
50
+ PLrDg7LoYgk9y8Wm50M57nutw5usWUkb-k
51
+ PLrDg7LoYgk9xvtY4y-8YhBPwP1FJgp3gZ
52
+ PLrDg7LoYgk9wciTWA7K9ZzAgTKoS1yy8x
53
+ PLrDg7LoYgk9zt7kMvJ1MBQTSQsnu8cDQo
54
+ PLrDg7LoYgk9yMaqz7kfpu8_hGgxocDgms
55
+ PLsHvWYTWngjXBizpITQXhblf9cYAnhxYx
56
+ PLRpcD8nxKTQpXodfJiEUMb9Dm_yTbgGvw
watched_videos.txt ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Ll3paP0BH6U
2
+ NuX6fthxQ0s
3
+ mjED2CJakRs
4
+ u2TT32s0_DM
5
+ a_JsgylN7Oo
6
+ WmlPEfxNRCE
7
+ N0Y_yntcEl0
8
+ GB9ai3w3F18
9
+ _s80uI4rYOk
10
+ GO-pX6XTLqQ
11
+ F0RcakGlerI
12
+ GftfOQNjPrs
13
+ pB85qqNSILk
14
+ gB_fXvAEed8
15
+ OuvX_Y3Ym9U
16
+ F0RcakGlerI
17
+ GftfOQNjPrs
18
+ pB85qqNSILk
19
+ gB_fXvAEed8
20
+ OuvX_Y3Ym9U
21
+ mlXmRnj4Mh4
22
+ MH27faYUxt4
23
+ uEsMGFtRPpE
24
+ bPOgTCU2wNY
25
+ 6jtetjtpKRI
26
+ pxwvoT0xQfg
27
+ swQx5sVHfJs
28
+ 6N3FfQFwF_c
29
+ vU5P9iy64LM
30
+ DNBHZauQrq8
31
+ LjZxf0lZ4pM
32
+ oXuCC4lBn2U
33
+ afUcA8JDWAU
34
+ uwocf01J6HY
35
+ HMlcZmlkU_s
36
+ 8P2nmXWoDeI
37
+ pjpa9yyU7LU
38
+ 7gV9cfjox0k
39
+ 6pOXxKpb-g8
40
+ gUxKIEsOzg8
41
+ 81XAqIH8s5w
42
+ _xk7abDhDmg
43
+ RMBPqkWXEmE
44
+ lEYTrye34Pk
45
+ I5jFErRW3P8
46
+ JIVAGr6J89Q
47
+ I7SGwkorc-c
48
+ y6l414cn9ZA
49
+ 6s3TkVu29l0
50
+ xntd7DLKOGQ
51
+ gxe69enf-Ss
52
+ YaAP9zg6HiA
53
+ tIWwDxBwYJI
54
+ RkGUFIq040w
55
+ NqEg8EBFBqI
56
+ rd432L-XlWE
57
+ dYC8LCmEj2U
58
+ ipqSqSLioEU
59
+ m3LKsAD6STY
60
+ m3LKsAD6STY
61
+ DexXw-PLmAQ
62
+ 4LZto4YAw0U
63
+ hCOuV35QIrU
64
+ TG0B8p2VEcA
65
+ X0lM0WhronU
66
+ K5_fHm6UWxw
67
+ 2go8lMVgGhs
68
+ QzyhKsfLKjg
69
+ vx_OoNQWpHE
70
+ cWDAMT1jK34
71
+ umPvBmosesA
72
+ cP5IZ28U7ew
73
+ pcQMC8sSZWg
74
+ A54ZnDHxs3A
75
+ A6h08eF9wo4
76
+ pfczeGQFoG0
77
+ -ESiCqP0I8I
78
+ LG_Ea150LTk
79
+ jOX_PZqAcA8
80
+ btto2wt_OUE
81
+ 8XqzgQ-DRG4
82
+ vt3rRiCySu8
83
+ fkOK1qfwRoU
84
+ KcHoTjnb4DA
85
+ detWGCCA15E
86
+ e6N5umDYZBA
87
+ vTAfdmrEZqY
88
+ Aakh1UX58m8
89
+ JVxNj36JtkY
90
+ V-aYmRqAjrs
91
+ Vo3ST1y_Afs
92
+ lQ9tR_tdjWw
93
+ K1bLXwZ8pTg
94
+ vtvjOwQL3zA
95
+ 9wsrISMdsgU
96
+ sPwqFSMO04c
97
+ eFdWFTFUUf0
98
+ Y1suHVKvS9A
99
+ 9rLERHGk4rA
100
+ O8Ecu56ETGE
101
+ fFAC2qPRGtg
102
+ O8Ecu56ETGE
103
+ fFAC2qPRGtg
104
+ O8Ecu56ETGE
105
+ fFAC2qPRGtg
106
+ LjkCNl0Km_0
107
+ s6dD4oW9GAQ
108
+ QAQMaQ2tt9w
109
+ jb4fP3G5MvQ
110
+ p1RjOjDkMaI
111
+ _8e4S7yvSjo
112
+ eKrtG0My9hY
113
+ qa6CjwFvIaM
114
+ HYg6OZuyzmg
115
+ TOyFCv-z4Rk
116
+ 4qP-ksCt9GM
117
+ zejuscOcyr0
118
+ -8Wg3TSOheU
119
+ IP958lpX-xA
120
+ nxLUCpWFSeI
121
+ K4Z8AZTeAAI
122
+ hq0K9X4W614
123
+ Ge9ycI_Zouk
124
+ CX3K1MNU7DI
125
+ hfBrKdvuCO4
126
+ F9HoFB8G8Ss
127
+ xVe4QdEJSgc
128
+ 9vPq6bQZhtA
129
+ X7YUEDMN8Vk
130
+ S8LT1pI6cv0
131
+ _zwwhay1YC4
132
+ aMCurbUpj7E
133
+ ZajLUqYu1uo
134
+ I0ZstVwam8o
135
+ AKxCMNsK7dg
136
+ N-Mz0JZgxwI
137
+ XXlqRvK0iHM
138
+ 7Pca37pWAzI
139
+ RlZx1kNqjXU
140
+ IkeBgumzgxA
141
+ ezlNArhzYdQ
142
+ r3I1p4PFTxA
143
+ OfCq1oP4Uxk
144
+ Huqb020SPoE
145
+ E3GzdyuENiI
146
+ MEcf9_SmaBE
147
+ zSFWhshyWmA
148
+ MK7HP6ZtRoM
149
+ c7Q2edn9aLQ
150
+ allu7D6we8A
151
+ -sY5J7p1o4Q
152
+ CzlFiRu38h0
153
+ u6RSHbp-jxs
154
+ QkJRBPSqrb4
155
+ T0nh6n-aw_g
156
+ 9IJgf0uzVDA
157
+ HoOOLQeG3JQ
158
+ BfKcFNYYTwk
159
+ g537eYfZt0M
160
+ h6C7-OK4rYA
161
+ VES5q3l1T2g
162
+ cXYNPd9za9M
163
+ 8Q8HW-DIE70
164
+ XNp7wth9YfU
165
+ UZZm3lpaLDs
166
+ RmU0Ms3JwQs
167
+ 3iLyN2ADM1g
168
+ xXl9chI-3FE
169
+ yu-Roz0i6x8
170
+ w0dv1Oyx9fg
171
+ 3cxWspXm0Gs
172
+ CWfBvm_RO6w
173
+ mYHzRP3YRtk
174
+ 9KbmlG_QkGM
175
+ sx7tR-_uENs
176
+ sj1D74G0pKg
177
+ cT0sXZHJgmA
178
+ tP20MGs2-bM
179
+ FfPUMe8MWAo
180
+ k2em5IQQ1aU
181
+ kgqQuX3DC2k
182
+ voHO7K-eXWo
183
+ al8qrPlPJ40
184
+ xcUkQhzqV-w
185
+ aKO8zqix7mI
186
+ gxP64wE-Qkc
187
+ 6hiTAi87hno
188
+ 2DRCi2ipXB0
189
+ -JNiaXL21MQ
190
+ QG47z8AKcMo
191
+ ZETGPWjqzOQ
192
+ lIxpFAncLsI
193
+ 9fK8wuOpLbM
194
+ Kd2vWEPzzuc
195
+ Y4sH7tcKR5Q
196
+ -x0_c6h1ShU
197
+ 1T0Gkhr7xTc
198
+ lBCtyXTnLQ8
199
+ e9cHLGBpJQI
200
+ l46VNo_0ftQ
201
+ w2g2wzUbidI
202
+ 6mHuKQak_nc
203
+ MYcfZxPkz08
204
+ Z8vYwKOOWLU
205
+ TnHB33igKxk
206
+ EoIf3Caiz1c
207
+ 4JG-BouURBU
208
+ nYy_UzDE2uI
209
+ wfLTYlsdct4
210
+ 3MIannraCzM
211
+ RAQH6k1eliA
212
+ 0tpd5QBQ8Ng
213
+ StOd1b3ukVI
214
+ V4xKp-FgZwg
215
+ NCTTv5GybjU
216
+ 7Wv36b2XsHY
217
+ W3aBrHLKp_s
218
+ 0gJ3Fda5fI4
219
+ AJc1lAWFSF0
220
+ 8lHLmdoX4w0
221
+ _ejbotKovEs
222
+ i808wRA5YF0
223
+ _ciuLxa8FeM
224
+ -LTwiOCeISI
225
+ sxeSu7aM8ow
226
+ x0PAmOYz0iU
227
+ ByGc9taE5eU
228
+ RD4nx0ZxqyY
229
+ WbA46jQO270
230
+ DmXxQuEuu3g
231
+ v1CY126AOxk
232
+ InhhJewd8bA
233
+ jGhelHxJgM8
234
+ Y5w0810FjQo
235
+ jI1CuyHRrvo
236
+ DmLvHuKkhB8
237
+ gLe-WkdMKYo
238
+ snTw3R_5S40
239
+ 6w1ZOo80eKo
240
+ 64xgYfLzByE
241
+ g2UMeUs5vUY
242
+ RYkG5nqqa_g
243
+ NrDTKiFnHOg
244
+ kpmwVVY1e9M
245
+ 0f-Fxrd1WAM
246
+ 7u5Emx99ww4
247
+ TjmJ6srrBsg
248
+ DWkO82OLrPg
249
+ PZlldo8Ya4g
250
+ 0h7Ovm2Di5s
251
+ BHZnhRkG7mc
252
+ sOPjjnjiudE
253
+ X0YEFHUgBpM
254
+ 6YXNgXu1DTY
255
+ pK3-Dj5P9i4
256
+ E9399RTFuEA
257
+ Hg4aMcEQhEA
258
+ Z8K8Dy2TQOU
259
+ O0thPienfrw
260
+ h1_VBcpO8_s
261
+ _flo7mnffFk
262
+ wcAFacgTmeA
263
+ 9Ej6bfQwjwc
264
+ sjAIjCQa4X4
265
+ K2f0fNQQ1_w
266
+ MtZa0nk3_Fw
267
+ 7RsRsmhg9pA
268
+ ER6lx3bTP_c
269
+ oK9cZTIM-O8
270
+ Bep6AL5Wy2c
271
+ vWCPXXEnJqE
272
+ I6VunhHMHxE
273
+ Avsa7hZE018
274
+ r0ena14Dtp0
275
+ 1ep0h-iAUnE
276
+ 914NDjjOlMs
277
+ agjokvj31I8
278
+ NqEBbbJABis
279
+ AOO-5FZgq4c