bep40 commited on
Commit
541d71e
·
verified ·
1 Parent(s): dac3747

Upload shorts_rss_proxy.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. shorts_rss_proxy.py +114 -0
shorts_rss_proxy.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ YouTube RSS Proxy - Fetches YouTube channel RSS feeds server-side
3
+ Avoids CORS issues when client tries to fetch YouTube directly
4
+ """
5
+ import requests as req
6
+ from fastapi import Query
7
+ from fastapi.responses import Response
8
+
9
+ HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
10
+
11
+ YOUTUBE_CHANNELS = {
12
+ "baodantri7941": "UC_x5TKhOgd6GhYvv5z4I3jg",
13
+ "baosuckhoedoisongboyte": "UCBsY5fXTQLkF_JnH9kLkL4g",
14
+ }
15
+
16
+ def setup_rss_proxy(app):
17
+ """Add RSS proxy endpoints to the FastAPI app"""
18
+
19
+ @app.get("/api/proxy/rss")
20
+ def proxy_rss(url: str = Query(...)):
21
+ """Proxy YouTube RSS feed to avoid CORS"""
22
+ try:
23
+ r = req.get(url, headers=HEADERS, timeout=15)
24
+ if r.status_code == 200:
25
+ return Response(
26
+ content=r.content,
27
+ media_type="application/xml",
28
+ headers={"Access-Control-Allow-Origin": "*"}
29
+ )
30
+ return Response(status_code=r.status_code)
31
+ except Exception as e:
32
+ return Response(status_code=502, content=str(e))
33
+
34
+ @app.get("/api/shorts/rss")
35
+ def shorts_via_rss():
36
+ """Get shorts from YouTube RSS feeds server-side"""
37
+ import xml.etree.ElementTree as ET
38
+ import html as html_lib
39
+ import re
40
+
41
+ shorts = []
42
+ seen = set()
43
+
44
+ for handle, channel_id in YOUTUBE_CHANNELS.items():
45
+ try:
46
+ rss_url = f"https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}"
47
+ r = req.get(rss_url, headers=HEADERS, timeout=15)
48
+ if r.status_code != 200:
49
+ continue
50
+
51
+ root = ET.fromstring(r.text)
52
+ ns = {
53
+ 'atom': 'http://www.w3.org/2005/Atom',
54
+ 'yt': 'http://www.youtube.com/xml/schemas/2015',
55
+ 'media': 'http://search.yahoo.com/mrss/'
56
+ }
57
+
58
+ for entry in root.findall('atom:entry', ns)[:30]:
59
+ title_el = entry.find('atom:title', ns)
60
+ title = html_lib.unescape(title_el.text) if title_el is not None and title_el.text else ''
61
+
62
+ link_el = entry.find('atom:link', ns)
63
+ link = link_el.get('href', '') if link_el is not None else ''
64
+
65
+ vid_el = entry.find('yt:videoId', ns)
66
+ vid = vid_el.text if vid_el is not None else ''
67
+
68
+ if not vid:
69
+ m = re.search(r'(?:v=|shorts/)([A-Za-z0-9_-]{11})', link)
70
+ if m:
71
+ vid = m.group(1)
72
+
73
+ if not vid or vid in seen:
74
+ continue
75
+
76
+ # Check if it's a short
77
+ is_short = '#shorts' in title.lower() or '#short' in title.lower() or '/shorts/' in link
78
+
79
+ if not is_short:
80
+ desc_el = entry.find('media:description', ns)
81
+ if desc_el is not None and desc_el.text:
82
+ if '#shorts' in desc_el.text.lower():
83
+ is_short = True
84
+
85
+ if not is_short:
86
+ continue
87
+
88
+ seen.add(vid)
89
+
90
+ # Get thumbnail
91
+ thumb = f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg"
92
+ media_group = entry.find('media:group', ns)
93
+ if media_group is not None:
94
+ thumb_el = media_group.find('media:thumbnail', ns)
95
+ if thumb_el is not None:
96
+ thumb = thumb_el.get('url', thumb)
97
+
98
+ shorts.append({
99
+ 'id': vid,
100
+ 'title': title.replace('#shorts', '').replace('#short', '').strip()[:120],
101
+ 'img': thumb,
102
+ 'link': f'https://www.youtube.com/shorts/{vid}',
103
+ 'channel': handle,
104
+ 'source': 'yt'
105
+ })
106
+
107
+ if len(shorts) >= 40:
108
+ break
109
+
110
+ except Exception as e:
111
+ print(f"RSS error for {handle}: {e}")
112
+ continue
113
+
114
+ return {"shorts": shorts, "count": len(shorts)}