Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- app.py +1403 -0
- listened_episodes.txt +139 -0
- playlists.txt +56 -0
- 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®=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®=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®=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
|