git-cli-yt 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.py +1 -11
- package/bin/cli.js +23 -2
- package/package.json +1 -1
- package/youtube/playlist.py +22 -41
- package/youtube/search.py +2 -3
package/app.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import threading
|
|
6
|
-
import os
|
|
7
6
|
import time
|
|
8
7
|
from typing import Any, Optional
|
|
9
8
|
|
|
@@ -582,17 +581,8 @@ class YouTubePlayer(App):
|
|
|
582
581
|
"Faça login para ver suas playlists.",
|
|
583
582
|
)
|
|
584
583
|
return
|
|
585
|
-
channel = auth.get_channel_info()
|
|
586
|
-
if not channel:
|
|
587
|
-
self.call_from_thread(
|
|
588
|
-
self._show_user_playlists_error,
|
|
589
|
-
generation,
|
|
590
|
-
"Não foi possível identificar o canal.",
|
|
591
|
-
)
|
|
592
|
-
return
|
|
593
584
|
items = youtube_playlist.fetch_user_playlists(
|
|
594
|
-
|
|
595
|
-
cookies_file=os.environ.get("YTDLP_COOKIES_FILE"),
|
|
585
|
+
auth.get_youtube_service(interactive=False)
|
|
596
586
|
)
|
|
597
587
|
self.call_from_thread(self._show_user_playlists, generation, items)
|
|
598
588
|
except Exception as exc:
|
package/bin/cli.js
CHANGED
|
@@ -8,10 +8,11 @@ const { execFileSync, spawn } = require("child_process");
|
|
|
8
8
|
|
|
9
9
|
const packageDir = path.join(__dirname, "..");
|
|
10
10
|
const isWindows = process.platform === "win32";
|
|
11
|
-
const venvDir = path.join(os.tmpdir(), "cli-yt-
|
|
11
|
+
const venvDir = path.join(os.tmpdir(), "cli-yt-venv");
|
|
12
12
|
const pythonExecutable = isWindows
|
|
13
13
|
? path.join(venvDir, "Scripts", "python.exe")
|
|
14
14
|
: path.join(venvDir, "bin", "python");
|
|
15
|
+
const pyvenvConfig = path.join(venvDir, "pyvenv.cfg");
|
|
15
16
|
const mainPy = path.join(packageDir, "main.py");
|
|
16
17
|
const requirementsFile = path.join(packageDir, "requirements.txt");
|
|
17
18
|
|
|
@@ -41,8 +42,28 @@ function findPython() {
|
|
|
41
42
|
throw new Error("Python 3.9 ou superior não foi encontrado.");
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function isValidVenv() {
|
|
46
|
+
if (!fs.existsSync(pythonExecutable) || !fs.existsSync(pyvenvConfig)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
execFileSync(
|
|
51
|
+
pythonExecutable,
|
|
52
|
+
["-c", "import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)"],
|
|
53
|
+
{ stdio: "ignore", windowsHide: true },
|
|
54
|
+
);
|
|
55
|
+
return true;
|
|
56
|
+
} catch (_) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
44
61
|
function setupVenv(python) {
|
|
45
|
-
if (!
|
|
62
|
+
if (!isValidVenv()) {
|
|
63
|
+
if (fs.existsSync(venvDir)) {
|
|
64
|
+
console.log("♻️ Recriando ambiente virtual inválido...");
|
|
65
|
+
fs.rmSync(venvDir, { recursive: true, force: true });
|
|
66
|
+
}
|
|
46
67
|
console.log("⚙️ Criando ambiente virtual Python isolado...");
|
|
47
68
|
execFileSync(
|
|
48
69
|
python.command,
|
package/package.json
CHANGED
package/youtube/playlist.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Listagem de playlists do YouTube via yt-dlp."""
|
|
1
|
+
"""Listagem de playlists do YouTube via API de dados e yt-dlp."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Any, Optional
|
|
7
7
|
|
|
8
8
|
import yt_dlp
|
|
9
|
+
from googleapiclient.errors import HttpError
|
|
9
10
|
|
|
10
11
|
from youtube.search import normalize_ytdlp_entry
|
|
11
12
|
|
|
@@ -14,31 +15,18 @@ class PlaylistError(RuntimeError):
|
|
|
14
15
|
"""Erro ao enumerar uma playlist."""
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
def fetch_user_playlists(
|
|
19
|
-
channel_id: str,
|
|
20
|
-
cookies_file: Optional[str] = None,
|
|
21
|
-
) -> list[dict[str, Any]]:
|
|
18
|
+
def fetch_user_playlists(youtube_service: Any) -> list[dict[str, Any]]:
|
|
22
19
|
"""Retorna Curtidas primeiro e depois as playlists do canal."""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
raise PlaylistError("Não foi possível identificar o canal do usuário.")
|
|
26
|
-
|
|
27
|
-
options: Any = {
|
|
28
|
-
"quiet": True,
|
|
29
|
-
"no_warnings": True,
|
|
30
|
-
"skip_download": True,
|
|
31
|
-
"extract_flat": True,
|
|
32
|
-
}
|
|
33
|
-
cookie_path = Path(cookies_file) if cookies_file else None
|
|
34
|
-
if cookie_path and cookie_path.exists():
|
|
35
|
-
options["cookiefile"] = str(cookie_path)
|
|
20
|
+
if youtube_service is None:
|
|
21
|
+
return []
|
|
36
22
|
|
|
37
|
-
url = f"https://www.youtube.com/channel/{channel_id}/playlists"
|
|
38
23
|
try:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
24
|
+
response = (
|
|
25
|
+
youtube_service.playlists()
|
|
26
|
+
.list(mine=True, part="snippet,contentDetails", maxResults=50)
|
|
27
|
+
.execute()
|
|
28
|
+
)
|
|
29
|
+
except HttpError as exc:
|
|
42
30
|
raise PlaylistError("Não foi possível listar as playlists do usuário.") from exc
|
|
43
31
|
|
|
44
32
|
results: list[dict[str, Any]] = [
|
|
@@ -51,31 +39,24 @@ def fetch_user_playlists(
|
|
|
51
39
|
"thumbnail": "",
|
|
52
40
|
}
|
|
53
41
|
]
|
|
54
|
-
for
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
playlist_id = str(entry.get("id", "")).strip()
|
|
58
|
-
playlist_url = str(
|
|
59
|
-
entry.get("webpage_url")
|
|
60
|
-
or entry.get("url")
|
|
61
|
-
or (f"https://www.youtube.com/playlist?list={playlist_id}" if playlist_id else "")
|
|
62
|
-
)
|
|
63
|
-
title = str(entry.get("title") or "Playlist sem título").strip()
|
|
64
|
-
if not playlist_id and not playlist_url:
|
|
42
|
+
for item in response.get("items", []):
|
|
43
|
+
playlist_id = str(item.get("id", "")).strip()
|
|
44
|
+
if not playlist_id:
|
|
65
45
|
continue
|
|
66
|
-
|
|
46
|
+
snippet = item.get("snippet", {})
|
|
47
|
+
thumbnails = snippet.get("thumbnails", {}) or {}
|
|
67
48
|
thumbnail = max(
|
|
68
|
-
thumbnails,
|
|
69
|
-
key=lambda
|
|
49
|
+
thumbnails.values(),
|
|
50
|
+
key=lambda value: value.get("width", 0),
|
|
70
51
|
default={},
|
|
71
52
|
)
|
|
72
53
|
results.append(
|
|
73
54
|
{
|
|
74
|
-
"id": playlist_id
|
|
55
|
+
"id": playlist_id,
|
|
75
56
|
"kind": "playlist",
|
|
76
|
-
"title": title,
|
|
77
|
-
"url":
|
|
78
|
-
"count":
|
|
57
|
+
"title": str(snippet.get("title") or "Playlist sem título").strip(),
|
|
58
|
+
"url": f"https://www.youtube.com/playlist?list={playlist_id}",
|
|
59
|
+
"count": item.get("contentDetails", {}).get("itemCount"),
|
|
79
60
|
"thumbnail": thumbnail.get("url", "")
|
|
80
61
|
if isinstance(thumbnail, dict)
|
|
81
62
|
else "",
|
package/youtube/search.py
CHANGED
|
@@ -231,7 +231,7 @@ def _build_results(
|
|
|
231
231
|
response: dict[str, Any],
|
|
232
232
|
service: Any = None,
|
|
233
233
|
api_key: Optional[str] = None,
|
|
234
|
-
) ->
|
|
234
|
+
) -> list[dict[str, Any]]:
|
|
235
235
|
items = response.get("items", [])
|
|
236
236
|
video_ids = [
|
|
237
237
|
item.get("id", {}).get("videoId", "")
|
|
@@ -281,8 +281,7 @@ def _build_results(
|
|
|
281
281
|
}
|
|
282
282
|
)
|
|
283
283
|
|
|
284
|
-
|
|
285
|
-
return results, str(next_page) if next_page else None
|
|
284
|
+
return results
|
|
286
285
|
|
|
287
286
|
|
|
288
287
|
def _video_details(
|