הורדת סרטונים דרך GitHub Actions במקום דרך שרת. תוסף Chrome שולח לוורקפלואו את הקישור,
את האיכות ואת העוגיות של הדפדפן, ה-runner מריץ yt-dlp, והתוסף מושך את התוצאה חזרה למחשב.
תוסף Chrome ──workflow_dispatch──▶ GitHub Actions ──yt-dlp──▶ artifact ──▶ חזרה לתוסף ──▶ תיקיית ההורדות
לכל הורדה נוצר request_id (UUID). הוורקפלואו מגדיר run-name: dl-<request_id> וגם קורא לארטיפקט
dl-<request_id>. התוסף מאתר את הריצה ואת הקובץ לפי אותו מזהה בדיוק — כך שכמה הורדות במקביל
לא מתערבבות זו בזו.
הריפו ציבורי, ולכן עוגיות לא נשלחות בגלוי: התוסף מצפין אותן ב-AES-256-GCM, והוורקפלואו מפענח אותן בתוך הריצה עם ה-secret. שני הצדדים חייבים להחזיק את אותו מפתח (64 תווי hex).
gh secret set COOKIE_KEY --repo <owner>/youtube-proxyאותו ערך נכנס גם לשדה cookieKey ב-extension/config.js (או דרך מסך ההגדרות של התוסף).
צריך fine-grained personal access token עם הרשאה אחת בלבד:
| הגדרה | ערך |
|---|---|
| Repository access | Only select repositories → הריפו הזה בלבד |
| Permissions → Actions | Read and write |
זה מספיק כדי להפעיל את הוורקפלואו, לקרוא סטטוס ריצה ולהוריד artifact — ולא נותן שום גישה אחרת. מפיקים אותו כאן: https://github.com/settings/personal-access-tokens/new
להטמעה נוחה:
python tools/embed_token.pyהסקריפט מבקש להדביק את הטוקן (הקלט מוסתר), מוודא מול ה-API שהוא אכן מגיע לוורקפלואו,
כותב אותו לתוך config.js לא כמחרוזת גלויה — אלא XOR עם מפתח אקראי, base64, ומפוצל
למספר קטעים מעורבבים — ובונה מחדש את ה-ZIP. יש בקובץ פונקציית _assembleToken() שמרכיבה
אותו בזמן ריצה. זה טשטוש (obfuscation) ולא הצפנה אמיתית — המפתח יושב באותו קובץ, אז מי
שקורא את config.js יכול לשחזר את הטוקן; המטרה היא רק שהוא לא יופיע כמחרוזת קריאה אחת.
ההגנה האמיתית היא ההרשאה המצומצמת של הטוקן עצמו (טבלה למעלה) — נבדק בפועל: 403 על כתיבת
git blob, 403 על קריאת secrets, ו-204 (הצלחה) רק על הפעלת הוורקפלואו הזה.
אפשר גם להדביק ידנית ב-extension/config.js, או במסך ההגדרות של התוסף (שם הוא נשמר גלוי
ב-chrome.storage.local, בלי הטשטוש — כי זה שדה עריכה רגיל).
chrome://extensions ← מצב מפתח ← "טעינת פריט לא ארוז" ← בוחרים את התיקייה extension/.
לחיצה על האייקון פותחת לשונית קבועה (לא פופאפ) — אפשר לסגור אותה בלי לפגוע בהורדה, ההורדה
עצמה רצה ב-background.js ולא תלויה בשום חלון פתוח. פותחים/מרעננים אותה כדי לראות את רשימת
ההורדות שוב; כל עבודה פעילה ממשיכה משם שהיא הייתה.
מדביקים קישור (או שהוא נטען לבד מהטאב הפעיל), בוחרים איכות ולוחצים "הורדה". העוגיות של
youtube.com ו-google.com נשלחות מוצפנות עם הבקשה, כך שסרטונים שדורשים חשבון מחובר עובדים גם הם.
ערוצים ופלייליסטים שלמים: קישור לערוץ (/channel/…, /c/…, /@handle) או לפלייליסט
(?list=… בלי v=) מזוהה אוטומטית ומדליק מתג "הורדת אוסף שלם" (אפשר גם להדליק ידנית). במצב
הזה כל הפריטים יורדים ונארזים ל-ZIP אחד בשם הערוץ/הפלייליסט, וזה מה שנשמר בתיקיית ההורדות —
לא קובץ בודד לכל סרטון. עד 200 פריטים לערוץ (נבדק בפועל עם ריצה אמיתית של 202 פריטים, ראו
מגבלות למטה); ריצה עם if: always() דואגת שה-ZIP ייבנה גם אם חלק מהפריטים נכשלו.
סרגל ההתקדמות משקף מה שגיטהאב באמת מדווח, בכל שלב אחרת:
- בהורדת סרטון בודד — אחוז השלמת השלבים בריצה (10–55%, מתוך Jobs API). לגיטהאב אין API
להתקדמות בתוך שלב בודד — נבדק בפועל: קריאת הלוגים החיים של ריצה שעדיין רצה מחזירה 404
(
BlobNotFound) — אז השלב "Download" (שיכול לקחת בין שנייה לכמה דקות) מוצג עם "זחילה" הדרגתית לכיוון האחוז הבא במקום קפיאה על מספר קבוע. - בהורדת ערוץ/פלייליסט — אחוזים אמיתיים לפי כמה פריטים כבר ירדו מתוך הסך הכול
(
5 מתוך 20 פריטים ירדו), כי כל פריט הוא Job נפרד בגיטהאב וה-API מדווח על סטטוס כל Job בנפרד. זו הסיבה שהורדת אוסף בנויה על כמה Jobs (enumerate→download-itemבמטריצה →package) ולא לולאה אחת בתוך ריצה בודדת — בלי הפיצול הזה אין דרך לדעת כמה פריטים כבר ירדו לפני שהריצה כולה מסתיימת.
ה-jobs נשמרים ב-chrome.storage.session, שלא שורד ריסטארט של הדפדפן (וטעינה מחדש של התוסף
בזמן פיתוח יכולה גם היא לנקות אותו) — אבל הריצה עצמה ממשיכה בגיטהאב בכל מקרה. בכל פתיחה של
הלשונית (וגם כל דקה ברקע) התוסף בודק אם יש ריצות workflow_dispatch שעדיין רצות בגיטהאב
ולא מוכרות לו מקומית, ומתחבר אליהן מחדש. בכוונה זה חל רק על ריצות שעדיין בעיצומן — ריצה
שכבר הסתיימה לא "מתאוששת" אוטומטית, כדי לא לשמור מחדש בלי שקט קובץ שכבר נשמר. מכיוון שגיטהאב
לא חושף את הפרמטרים המקוריים של workflow_dispatch אחרי השליחה, ריצה משוחזרת לא יודעת את
הכתובת/כותרת/איכות המקוריים (ולכן גם אין לה כפתור "נסה שוב") — אבל היא כן ממשיכה לעקוב אחרי
הריצה, מושכת את הקובץ בסיום, ומציגה את השם האמיתי מ-yt-dlp כמו כל הורדה אחרת.
| קובץ | תפקיד |
|---|---|
.github/workflows/download.yml |
הוורקפלואו: resolve-video (וידאו בודד) או enumerate→download-item (מטריצה)→package (אוסף שלם) |
extension/background.js |
שולח dispatch, עוקב אחרי הריצה (כולל התקדמות אמיתית לפי Jobs), שומר את הקובץ, מתחבר מחדש לריצות קיימות |
extension/offscreen.js |
מוריד את ה-ZIP בזרימה (עם דיווח התקדמות) ופורק אותו |
extension/panel.html / panel.js |
הממשק: קישור, איכות, זיהוי אוסף, רשימת הורדות עם התקדמות |
extension/options.js |
ריפו, טוקן, מפתח הצפנה |
tools/make_icons.py |
מייצר את האייקונים מחדש |
tools/embed_token.py |
מטמיע טוקן חדש בצורה מטושטשת ובונה מחדש את ה-ZIP |
- הריפו ציבורי, ולכן גם ה-artifacts ציבוריים. כל מי שיש לו קישור לריצה יכול להוריד את הקובץ
במשך יום (
retention-days: 1). העוגיות עצמן מוצפנות, אבל הסרטון שהורדתם — לא. אם זה מפריע,gh repo edit --visibility privateפותר את זה (במחיר של מכסת דקות חודשית). - יוטיוב חוסם כתובות IP של דאטה-סנטר. בלי עוגיות של חשבון מחובר, כל ריצה מיוטיוב נופלת על
Sign in to confirm you're not a bot— נבדק, זה מה שקורה. בדיוק בשביל זה התוסף שולח את העוגיות שלכם. שווה לדעת שיוטיוב עלול לפסול עוגיות שנעשה בהן שימוש מכתובת אחרת, ולכן עדיף חשבון נפרד ולא החשבון הראשי. אתרים אחרים וקישורים ישירים עובדים גם בלי עוגיות בכלל. - קובץ (או ZIP אחד) לכל ריצה, עד 45 דקות לכל Job בנפרד — ערוץ גדול מאוד עלול לא להספיק בזמן הזה.
- מצב אוסף מוגבל ל-200 פריטים (
--playlist-end 200); ערוץ גדול יותר יוריד רק את ה-200 הראשונים, עם::notice::בלוג הריצה. פריט בודד שנכשל לא מפיל את כל ההורדה (continue-on-errorברמת ה-Job) — הוא פשוט לא נכנס ל-ZIP, וזה מוצג בפרטי ההתקדמות ("X נכשלו"). - עד 8 פריטים במקביל בהורדת אוסף (
max-parallel: 8) — כדי לא להציף את הריפו בעשרות Jobs בו-זמנית.