dragxd commited on
Commit
b519558
·
1 Parent(s): 1d98197

Add all project files for Telegram Mini Web App

Browse files
Files changed (6) hide show
  1. Dockerfile +12 -0
  2. app.py +40 -0
  3. requirements.txt +6 -0
  4. static/index.html +76 -0
  5. telegram_bot.py +23 -0
  6. youtube_api.py +87 -0
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt ./
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 7860
11
+
12
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Query, HTTPException
2
+ from fastapi.responses import JSONResponse, RedirectResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ from youtube_api import YouTubeAPI
5
+ import uvicorn
6
+
7
+ app = FastAPI()
8
+ app.mount("/static", StaticFiles(directory="static"), name="static")
9
+ yt_api = YouTubeAPI()
10
+
11
+ @app.get("/")
12
+ def root():
13
+ return RedirectResponse(url="/static/index.html")
14
+
15
+ @app.get("/search")
16
+ async def search(q: str = Query(..., description="Search query")):
17
+ results = await yt_api.search(q, limit=10)
18
+ # Only return relevant fields
19
+ filtered = [
20
+ {
21
+ "title": r["title"],
22
+ "duration": r["duration"],
23
+ "id": r["id"],
24
+ "link": r["link"],
25
+ "thumbnails": r["thumbnails"],
26
+ }
27
+ for r in results
28
+ ]
29
+ return JSONResponse(filtered)
30
+
31
+ @app.get("/stream")
32
+ async def stream(url: str = Query(..., description="YouTube video URL")):
33
+ try:
34
+ stream_url = await yt_api.get_stream_url(url, audio_only=True)
35
+ return RedirectResponse(url=stream_url)
36
+ except Exception as e:
37
+ raise HTTPException(status_code=500, detail=str(e))
38
+
39
+ if __name__ == "__main__":
40
+ uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ yt-dlp
4
+ youtubesearchpython
5
+ httpx
6
+ python-multipart
static/index.html ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mini Web App Music Player</title>
7
+ <style>
8
+ body { font-family: Arial, sans-serif; margin: 2em; background: #f9f9f9; }
9
+ #results img { width: 80px; border-radius: 8px; }
10
+ #results { margin-top: 1em; }
11
+ .result { display: flex; align-items: center; margin-bottom: 1em; background: #fff; padding: 1em; border-radius: 8px; box-shadow: 0 2px 8px #0001; }
12
+ .info { margin-left: 1em; }
13
+ .play-btn { margin-left: auto; padding: 0.5em 1em; background: #007bff; color: #fff; border: none; border-radius: 4px; cursor: pointer; }
14
+ .play-btn:hover { background: #0056b3; }
15
+ #player { margin-top: 2em; width: 100%; }
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <h1>Mini Web App Music Player</h1>
20
+ <form id="searchForm">
21
+ <input type="text" id="query" placeholder="Search for a song or artist..." size="40" required>
22
+ <button type="submit">Search</button>
23
+ </form>
24
+ <div id="results"></div>
25
+ <audio id="player" controls style="display:none;"></audio>
26
+ <script src="https://telegram.org/js/telegram-web-app.js"></script>
27
+ <script>
28
+ // Telegram Web App integration
29
+ if (window.Telegram && Telegram.WebApp) {
30
+ Telegram.WebApp.ready();
31
+ document.body.style.background = Telegram.WebApp.themeParams.bg_color || '#f9f9f9';
32
+ // Optionally show user info
33
+ const user = Telegram.WebApp.initDataUnsafe.user;
34
+ if (user) {
35
+ const info = document.createElement('div');
36
+ info.innerHTML = `<b>Hi, ${user.first_name}${user.last_name ? ' ' + user.last_name : ''}!</b> <small>(via Telegram Mini App)</small>`;
37
+ document.body.insertBefore(info, document.body.firstChild.nextSibling);
38
+ }
39
+ }
40
+ const form = document.getElementById('searchForm');
41
+ const resultsDiv = document.getElementById('results');
42
+ const player = document.getElementById('player');
43
+
44
+ form.onsubmit = async (e) => {
45
+ e.preventDefault();
46
+ resultsDiv.innerHTML = 'Searching...';
47
+ const q = document.getElementById('query').value;
48
+ const res = await fetch(`/search?q=${encodeURIComponent(q)}`);
49
+ const results = await res.json();
50
+ if (!results.length) {
51
+ resultsDiv.innerHTML = 'No results found.';
52
+ return;
53
+ }
54
+ resultsDiv.innerHTML = '';
55
+ results.forEach(r => {
56
+ const div = document.createElement('div');
57
+ div.className = 'result';
58
+ div.innerHTML = `
59
+ <img src="${r.thumbnails[0].url}" alt="thumb">
60
+ <div class="info">
61
+ <div><b>${r.title}</b></div>
62
+ <div>Duration: ${r.duration || 'N/A'}</div>
63
+ </div>
64
+ <button class="play-btn">Play</button>
65
+ `;
66
+ div.querySelector('.play-btn').onclick = () => {
67
+ player.src = `/stream?url=${encodeURIComponent(r.link)}`;
68
+ player.style.display = 'block';
69
+ player.play();
70
+ };
71
+ resultsDiv.appendChild(div);
72
+ });
73
+ };
74
+ </script>
75
+ </body>
76
+ </html>
telegram_bot.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from telegram import Update, KeyboardButton, ReplyKeyboardMarkup
2
+ from telegram.ext import Application, CommandHandler, ContextTypes
3
+ import os
4
+
5
+ # Set your bot token and web app URL here
6
+ BOT_TOKEN = os.getenv("BOT_TOKEN", "YOUR_BOT_TOKEN")
7
+ WEB_APP_URL = os.getenv("WEB_APP_URL", "https://xfasfadgagsg--miniwebapp.hf.space") # Replace with your actual Hugging Face Space URL
8
+
9
+ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
10
+ keyboard = [
11
+ [KeyboardButton("🎵 Open Music Player", web_app={"url": WEB_APP_URL})]
12
+ ]
13
+ reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True)
14
+ await update.message.reply_text(
15
+ "Welcome! Tap below to open the music player:",
16
+ reply_markup=reply_markup
17
+ )
18
+
19
+ if __name__ == "__main__":
20
+ app = Application.builder().token(BOT_TOKEN).build()
21
+ app.add_handler(CommandHandler("start", start))
22
+ print("Bot is running. Send /start to your bot in Telegram.")
23
+ app.run_polling()
youtube_api.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import os
3
+ import re
4
+ import httpx
5
+ import yt_dlp
6
+ from youtubesearchpython.__future__ import VideosSearch
7
+ import tempfile
8
+ from typing import Union
9
+
10
+ async def shell_cmd(cmd):
11
+ proc = await asyncio.create_subprocess_shell(
12
+ cmd,
13
+ stdout=asyncio.subprocess.PIPE,
14
+ stderr=asyncio.subprocess.PIPE,
15
+ )
16
+ out, errorz = await proc.communicate()
17
+ if errorz:
18
+ if "unavailable videos are hidden" in (errorz.decode("utf-8")).lower():
19
+ return out.decode("utf-8")
20
+ else:
21
+ return errorz.decode("utf-8")
22
+ return out.decode("utf-8")
23
+
24
+ class YouTubeAPI:
25
+ def __init__(self):
26
+ self.base = "https://www.youtube.com/watch?v="
27
+ self.regex = r"(?:youtube\.com|youtu\.be)"
28
+ self.status = "https://www.youtube.com/oembed?url="
29
+ self.listbase = "https://youtube.com/playlist?list="
30
+ self.reg = re.compile(r"\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])")
31
+
32
+ async def exists(self, link: str, videoid: Union[bool, str] = None):
33
+ if videoid:
34
+ link = self.base + link
35
+ if re.search(self.regex, link):
36
+ return True
37
+ else:
38
+ return False
39
+
40
+ async def details(self, link: str, videoid: Union[bool, str] = None):
41
+ if videoid:
42
+ link = self.base + link
43
+ if "&" in link:
44
+ link = link.split("&")[0]
45
+ results = VideosSearch(link, limit=1)
46
+ for result in (await results.next())["result"]:
47
+ title = result["title"]
48
+ duration_min = result["duration"]
49
+ thumbnail = result["thumbnails"][0]["url"].split("?")[0]
50
+ vidid = result["id"]
51
+ if str(duration_min) == "None":
52
+ duration_sec = 0
53
+ else:
54
+ duration_sec = self.time_to_seconds(duration_min)
55
+ return title, duration_min, duration_sec, thumbnail, vidid
56
+
57
+ async def search(self, query: str, limit: int = 10):
58
+ results = VideosSearch(query, limit=limit)
59
+ return (await results.next())["result"]
60
+
61
+ async def get_stream_url(self, link: str, audio_only: bool = True):
62
+ if "&" in link:
63
+ link = link.split("&")[0]
64
+ ydl_opts = {
65
+ "quiet": True,
66
+ "format": "bestaudio/best" if audio_only else "best[height<=?720][width<=?1280]",
67
+ "noplaylist": True,
68
+ }
69
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
70
+ info = ydl.extract_info(link, download=False)
71
+ if audio_only:
72
+ for f in info["formats"]:
73
+ if f.get("acodec") != "none" and f.get("vcodec") == "none":
74
+ return f["url"]
75
+ return info["url"]
76
+ else:
77
+ return info["url"]
78
+
79
+ def time_to_seconds(self, time_str):
80
+ parts = time_str.split(":")
81
+ parts = [int(p) for p in parts]
82
+ if len(parts) == 3:
83
+ return parts[0] * 3600 + parts[1] * 60 + parts[2]
84
+ elif len(parts) == 2:
85
+ return parts[0] * 60 + parts[1]
86
+ else:
87
+ return int(parts[0])