summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--locales/ro.json336
-rw-r--r--shard.yml2
-rw-r--r--src/invidious.cr10
-rw-r--r--src/invidious/helpers/jobs.cr54
-rw-r--r--src/invidious/helpers/utils.cr12
5 files changed, 405 insertions, 9 deletions
diff --git a/locales/ro.json b/locales/ro.json
new file mode 100644
index 00000000..75496a01
--- /dev/null
+++ b/locales/ro.json
@@ -0,0 +1,336 @@
+{
+ "`x` subscribers": "`x` abonați",
+ "`x` videos": "`x` videoclipuri",
+ "`x` playlists": "`x` liste de redare",
+ "LIVE": "ÎN DIRECT",
+ "Shared `x` ago": "Adăugat acum `x`",
+ "Unsubscribe": "Dezabonați-vă",
+ "Subscribe": "Abonați-vă",
+ "View channel on YouTube": "Vedeți canalul pe YouTube",
+ "View playlist on YouTube": "Vedeți lista de redare pe YouTube",
+ "newest": "Data adăugării (cea mai recentă)",
+ "oldest": "Data adăugării (cea mai veche)",
+ "popular": "Cele mai populare",
+ "last": "Ultimele",
+ "Next page": "Pagina următoare",
+ "Previous page": "Pagina precedentă",
+ "Clear watch history?": "Doriți să ștergeți istoricul?",
+ "New password": "Parola nouă",
+ "New passwords must match": "Câmpurile \"Parolă nouă\" trebuie să fie identice",
+ "Cannot change password for Google accounts": "Parola pentru un cont Google nu poate fi schimbată de pe Invidious",
+ "Authorize token?": "Autorizați token-ul?",
+ "Authorize token for `x`?": "Autorizați token-ul pentru `x` ?",
+ "Yes": "Da",
+ "No": "Nu",
+ "Import and Export Data": "Importați și Exportați Datele",
+ "Import": "Importați",
+ "Import Invidious data": "Importați Datele de pe Invidious",
+ "Import YouTube subscriptions": "Importați abonamentele de pe YouTube",
+ "Import FreeTube subscriptions (.db)": "Importați abonamentele de pe FreeTube (.db)",
+ "Import NewPipe subscriptions (.json)": "Importați abonamentele de pe NewPipe (.json)",
+ "Import NewPipe data (.zip)": "Importați datele de pe NewPipe (.zip)",
+ "Export": "Exportați",
+ "Export subscriptions as OPML": "Exportați abonamentele în format OPML",
+ "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportați abonamentele în format OPML (pentru NewPipe și FreeTube)",
+ "Export data as JSON": "Exportați datele în format JSON",
+ "Delete account?": "Sunteți siguri că doriți să vă ștergeți contul?",
+ "History": "Istoric",
+ "An alternative front-end to YouTube": "O alternativă front-end pentru YouTube",
+ "JavaScript license information": "Informații despre licențele JavaScript",
+ "source": "sursă",
+ "Log in": "Conectați-vă",
+ "Log in/register": "Conectați-vă/Creați-vă un cont",
+ "Log in with Google": "Conectați-vă cu Google",
+ "User ID": "ID Utilizator",
+ "Password": "Parolă",
+ "Time (h:mm:ss):": "Ora (h:mm:ss) :",
+ "Text CAPTCHA": "Text CAPTCHA",
+ "Image CAPTCHA": "Imagine CAPTCHA",
+ "Sign In": "Conectați-vă",
+ "Register": "Înregistrați-vă",
+ "E-mail": "E-mail",
+ "Google verification code": "Cod de verificare Google",
+ "Preferences": "Preferințe",
+ "Player preferences": "Setări de redare",
+ "Always loop: ": "Reluați videoclipul la nesfârșit: ",
+ "Autoplay: ": "Porniți videoclipurile automat: ",
+ "Play next by default: ": "Vizionați următoarele videoclipuri în mod implicit: ",
+ "Autoplay next video: ": "Porniți următorul videoclip automat: ",
+ "Listen by default: ": "Numai audio: ",
+ "Proxy videos: ": "Redați videoclipurile printr-un proxy: ",
+ "Default speed: ": "Viteza de redare implicită: ",
+ "Preferred video quality: ": "Calitatea videoclipurilor: ",
+ "Player volume: ": "Volumul videoclipurilor: ",
+ "Default comments: ": "Sursa comentariilor: ",
+ "youtube": "YouTube",
+ "reddit": "Reddit",
+ "Default captions: ": "Subtitrări implicite: ",
+ "Fallback captions: ": "Subtitrări alternative: ",
+ "Show related videos: ": "Afișați videoclipurile asemănătoare: ",
+ "Show annotations by default: ": "Afișați adnotările în mod implicit: ",
+ "Visual preferences": "Preferințele site-ului",
+ "Player style: ": "Stilul player-ului : ",
+ "Dark mode: ": "Modul întunecat : ",
+ "Theme: ": "Tema : ",
+ "dark": "întunecat",
+ "light": "luminos",
+ "Thin mode: ": "Mod lejer: ",
+ "Subscription preferences": "Preferințele paginii de abonamente",
+ "Show annotations by default for subscribed channels: ": "Afișați adnotările în mod implicit pentru canalele la care v-ați abonat: ",
+ "Redirect homepage to feed: ": "Redirecționați pagina principală la pagina de abonamente: ",
+ "Number of videos shown in feed: ": "Numărul de videoclipuri afișate pe pagina de abonamente: ",
+ "Sort videos by: ": "Sortați videoclipurile în funcție de: ",
+ "published": "data publicării",
+ "published - reverse": "data publicării - inversată",
+ "alphabetically": "în ordine alfabetică",
+ "alphabetically - reverse": "în ordine alfabetică - inversată",
+ "channel name": "numele canalului",
+ "channel name - reverse": "numele canalului - inversat",
+ "Only show latest video from channel: ": "Afișați numai cel mai recent videoclip publicat de canalele la care v-ați abonat: ",
+ "Only show latest unwatched video from channel: ": "Afișați numai cel mai recent videoclip nevizionat publicat de canalele la care v-ați abonat: ",
+ "Only show unwatched: ": "Afișați numai videoclipurile nevizionate: ",
+ "Only show notifications (if there are any): ": "Afișați numai notificările (dacă există): ",
+ "Enable web notifications": "Activați notificările web",
+ "`x` uploaded a video": "`x` a publicat un videoclip",
+ "`x` is live": "`x` este în direct",
+ "Data preferences": "Preferințe legate de date",
+ "Clear watch history": "Ștergeți istoricul videoclipurilor vizionate",
+ "Import/export data": "Importați/exportați datele",
+ "Change password": "Schimbați parola",
+ "Manage subscriptions": "Gestionați abonamentele",
+ "Manage tokens": "Gestionați tokenele",
+ "Watch history": "Istoricul videoclipurilor vizionate",
+ "Delete account": "Ștergeți contul",
+ "Administrator preferences": "Preferințele Administratorului",
+ "Default homepage: ": "Pagina principală implicită: ",
+ "Feed menu: ": "Preferințe legate de pagina de abonamente: ",
+ "Top enabled: ": "Top activat: ",
+ "CAPTCHA enabled: ": "CAPTCHA activat : ",
+ "Login enabled: ": "Autentificare activată : ",
+ "Registration enabled: ": "Înregistrate activată: ",
+ "Report statistics: ": "Raportarea statisticilor: ",
+ "Save preferences": "Salvați preferințele",
+ "Subscription manager": "Gestionați abonamentele",
+ "Token manager": "Manager de Tokene",
+ "Token": "Token",
+ "`x` subscriptions": "`x` abonamente",
+ "`x` tokens": "`x` tokens",
+ "Import/export": "Importați/Exportați",
+ "unsubscribe": "dezabonați-vă",
+ "revoke": "revocați",
+ "Subscriptions": "Abonamente",
+ "`x` unseen notifications": "`x` notificări nevăzute",
+ "search": "căutați",
+ "Log out": "Deconectați-vă",
+ "Released under the AGPLv3 by Omar Roth.": "Publicat sub licența AGPLv3 de Omar Roth.",
+ "Source available here.": "Codul sursă este disponibil aici.",
+ "View JavaScript license information.": "Informații legate de licența JavaScript.",
+ "View privacy policy.": "Politica de confidențialitate.",
+ "Trending": "Tendințe",
+ "Public": "Public",
+ "Unlisted": "Necatalogat",
+ "Private": "Privat",
+ "View all playlists": "Afișați toate listele de redare",
+ "Updated `x` ago": "Actualizat acum `x`",
+ "Delete playlist `x`?": "Sigur doriți să ștergeți lista de redare?",
+ "Delete playlist": "Ștergeți lista de redare",
+ "Create playlist": "Creați o listă de redare",
+ "Title": "Titlu",
+ "Playlist privacy": "Parametrii de confidențialitate ai listei de redare",
+ "Editing playlist `x`": "Modificați lista de redare `x`",
+ "Watch on YouTube": "Urmăriți videoclipul pe YouTube",
+ "Hide annotations": "Ascundeți adnotările",
+ "Show annotations": "Afișați adnotările",
+ "Genre: ": "Categorie: ",
+ "License: ": "Licență: ",
+ "Family friendly? ": "Adecvat pentru întreaga familie? ",
+ "Wilson score: ": "Scor Wilson: ",
+ "Engagement: ": "Procentul celor care au apăsat pe \"Îmi place\" sau \"Nu îmi place\" : ",
+ "Whitelisted regions: ": "Regiunile de pe lista albă: ",
+ "Blacklisted regions: ": "Regiunile de pe lista neagră: ",
+ "Shared `x`": "Publicat pe `x`",
+ "`x` views": "`x` vizionări",
+ "Premieres in `x`": "Premiera în `x`",
+ "Premieres `x`": "Premiera pe `x`",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Se pare că ați dezactivat JavaScript. Apăsați aici pentru a vizualiza comentariile. Țineți minte faptul că încărcarea lor ar putea să dureze puțin mai mult.",
+ "View YouTube comments": "Vedeți comentariile de pe YouTube",
+ "View more comments on Reddit": "Vedeți mai multe comentarii pe Reddit",
+ "View `x` comments": "Afișați `x` comentarii",
+ "View Reddit comments": "Afișați comentariile de pe Reddit",
+ "Hide replies": "Ascundeți replicile",
+ "Show replies": "Afișați replicile",
+ "Incorrect password": "Parolă incorectă",
+ "Quota exceeded, try again in a few hours": "Numărul de tentative de conectare a fost depășit. Va rugăm să încercați din nou în câteva ore.",
+ "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Conectare eșuată. Dacă nu reușiți să vă conectați, verificați dacă ați activat autentificarea cu doi factori (Autentificator sau SMS).",
+ "Invalid TFA code": "Codul de autentificare cu doi factori este invalid",
+ "Login failed. This may be because two-factor authentication is not turned on for your account.": "Conectare eșuată. Acest lucru ar putea fi cauzat de faptul că nu ați activat autentificarea cu doi factori.",
+ "Wrong answer": "Răspuns invalid",
+ "Erroneous CAPTCHA": "CAPTCHA invalid",
+ "CAPTCHA is a required field": "Câmpul CAPTCHA este obligatoriu",
+ "User ID is a required field": "Câmpul ID Utilizator este obligatoriu",
+ "Password is a required field": "Câmpul Parolă este obligatoriu",
+ "Wrong username or password": "Nume de utilizator sau parolă invalidă",
+ "Please sign in using 'Log in with Google'": "Vă rog conectați-vă folosind \"Conectați-vă cu Google\"",
+ "Password cannot be empty": "Parola nu poate fi goală",
+ "Password cannot be longer than 55 characters": "Parola nu poate să conțină mai mult de 55 de caractere",
+ "Please log in": "Vă rog conectați-vă",
+ "Invidious Private Feed for `x`": "Feed RSS privat pentru `x`",
+ "channel:`x`": "canal:`x`",
+ "Deleted or invalid channel": "Canal șters sau invalid",
+ "This channel does not exist.": "Acest canal nu există.",
+ "Could not get channel info.": "Nu am putut primi informații despre acest canal.",
+ "Could not fetch comments": "Încărcarea comentariilor a eșuat.",
+ "View `x` replies": "Afișați `x` replici",
+ "`x` ago": "acum `x`",
+ "Load more": "Vedeți mai mult",
+ "`x` points": "`x` puncte",
+ "Could not create mix.": "Nu am putut crea această listă de redare.",
+ "Empty playlist": "Lista de redare este goală",
+ "Not a playlist.": "Lista de redare este invalidă.",
+ "Playlist does not exist.": "Această listă de redare nu există.",
+ "Could not pull trending pages.": "Încărcarea paginilor de tendințe a eșuat.",
+ "Hidden field \"challenge\" is a required field": "Câmpul ascuns \"challenge\" este un câmp obligatoriu",
+ "Hidden field \"token\" is a required field": "Câmpul ascuns \"token\" este un câmp obligatoriu",
+ "Erroneous challenge": "Challenge invalid",
+ "Erroneous token": "Token invalid",
+ "No such user": "Acest utilizator nu există",
+ "Token is expired, please try again": "Token-ul este expirat, vă rugăm să reîncercați.",
+ "English": "Engleză",
+ "English (auto-generated)": "Engleză (generată automat)",
+ "Afrikaans": "Afrikaans",
+ "Albanian": "Albaneză",
+ "Amharic": "Amharică",
+ "Arabic": "Arabă",
+ "Armenian": "Arméniană",
+ "Azerbaijani": "Azeră",
+ "Bangla": "Bangla",
+ "Basque": "Basque",
+ "Belarusian": "Belarusă",
+ "Bosnian": "Bosniacă",
+ "Bulgarian": "Bulgară",
+ "Burmese": "Birmană",
+ "Catalan": "Catalană",
+ "Cebuano": "Cebuano",
+ "Chinese (Simplified)": "Chineză (Simplificată)",
+ "Chinese (Traditional)": "Chinois (Tradițională)",
+ "Corsican": "Corsicană",
+ "Croatian": "Croată",
+ "Czech": "Cehă",
+ "Danish": "Daneză",
+ "Dutch": "Olandeză",
+ "Esperanto": "Esperanto",
+ "Estonian": "Estoniană",
+ "Filipino": "Filipineză",
+ "Finnish": "Finlandeză",
+ "French": "Franceză",
+ "Galician": "Galiciană",
+ "Georgian": "Georgiană",
+ "German": "Germană",
+ "Greek": "Greacă",
+ "Gujarati": "Gujarati",
+ "Haitian Creole": "Creola Haitiană",
+ "Hausa": "Haousa",
+ "Hawaiian": "Hawaiană",
+ "Hebrew": "Ebraică",
+ "Hindi": "Hindi",
+ "Hmong": "Hmong",
+ "Hungarian": "Ungară",
+ "Icelandic": "Islandeză",
+ "Igbo": "Igbo",
+ "Indonesian": "Indoneziană",
+ "Irish": "Irlandeză",
+ "Italian": "Italiană",
+ "Japanese": "Japoneză",
+ "Javanese": "Javaneză",
+ "Kannada": "Kannada",
+ "Kazakh": "Kazakh",
+ "Khmer": "Khmer",
+ "Korean": "Coreană",
+ "Kurdish": "Kurdă",
+ "Kyrgyz": "Kirghize",
+ "Lao": "Lao",
+ "Latin": "Latină",
+ "Latvian": "Letonă",
+ "Lithuanian": "Lituaniană",
+ "Luxembourgish": "Luxemburgheză",
+ "Macedonian": "Macedoniană",
+ "Malagasy": "Malgașă",
+ "Malay": "Malaieză",
+ "Malayalam": "Malayalam",
+ "Maltese": "Malteză",
+ "Maori": "Maori",
+ "Marathi": "Marathi",
+ "Mongolian": "Mongoliană",
+ "Nepali": "Nepaleză",
+ "Norwegian Bokmål": "Norvegiană",
+ "Nyanja": "Nyanja",
+ "Pashto": "Pachtou",
+ "Persian": "Persană",
+ "Polish": "Poloneză",
+ "Portuguese": "Portugheză",
+ "Punjabi": "Punjabi",
+ "Romanian": "Română",
+ "Russian": "Rusă",
+ "Samoan": "Samoan",
+ "Scottish Gaelic": "Galic Scoțian",
+ "Serbian": "Sârbă",
+ "Shona": "Shona",
+ "Sindhi": "Sindhi",
+ "Sinhala": "Sinhala",
+ "Slovak": "Slovacă",
+ "Slovenian": "Slovenă",
+ "Somali": "Somaleză",
+ "Southern Sotho": "Sotho de Sud",
+ "Spanish": "Spaniolă",
+ "Spanish (Latin America)": "Spaniolă (America Latină)",
+ "Sundanese": "Sundaneză",
+ "Swahili": "Swahili",
+ "Swedish": "Suedeză",
+ "Tajik": "Tajik",
+ "Tamil": "Tamil",
+ "Telugu": "Telugu",
+ "Thai": "Tailandeză",
+ "Turkish": "Turcă",
+ "Ukrainian": "Ucrainiană",
+ "Urdu": "Urdu",
+ "Uzbek": "Uzbek",
+ "Vietnamese": "Vietnameză",
+ "Welsh": "Galeză",
+ "Western Frisian": "Frisiană de Vest",
+ "Xhosa": "Xhosa",
+ "Yiddish": "Yiddish",
+ "Yoruba": "Yoruba",
+ "Zulu": "Zoulou",
+ "`x` years": "`x` ani",
+ "`x` months": "`x` luni",
+ "`x` weeks": "`x` săptămâni",
+ "`x` days": "`x` zile",
+ "`x` hours": "`x` ore",
+ "`x` minutes": "`x` minute",
+ "`x` seconds": "`x` secunde",
+ "Fallback comments: ": "Comentarii alternative: ",
+ "Popular": "Popular",
+ "Top": "Top",
+ "About": "Despre",
+ "Rating: ": "Evaluare: ",
+ "Language: ": "Limbă: ",
+ "View as playlist": "Vizualizați ca listă de redare",
+ "Default": "Implicit",
+ "Music": "Muzică",
+ "Gaming": "Jocuri Video",
+ "News": "Noutăți",
+ "Movies": "Filme",
+ "Download": "Descărcați",
+ "Download as: ": "Descărcați ca: ",
+ "%A %B %-d, %Y": "%A %-d %B %Y",
+ "(edited)": "(editat)",
+ "YouTube comment permalink": "Permalink pentru comentariul de pe YouTube",
+ "permalink": "permalink",
+ "`x` marked it with a ❤": "`x` l-a marcat cu o ❤",
+ "Audio mode": "Mod audio",
+ "Video mode": "Mod video",
+ "Videos": "Videoclipuri",
+ "Playlists": "Liste de redare",
+ "Community": "Comunitate",
+ "Current version: ": "Versiunea actuală: "
+} \ No newline at end of file
diff --git a/shard.yml b/shard.yml
index 6287ca63..d5901a78 100644
--- a/shard.yml
+++ b/shard.yml
@@ -26,7 +26,7 @@ dependencies:
version: ~> 0.1.2
lsquic:
github: omarroth/lsquic.cr
- version: ~> 0.1.5
+ version: ~> 0.1.7
crystal: 0.31.1
diff --git a/src/invidious.cr b/src/invidious.cr
index 147fe935..a4f20bc4 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -87,6 +87,7 @@ LOCALES = {
"nb_NO" => load_locale("nb_NO"),
"nl" => load_locale("nl"),
"pl" => load_locale("pl"),
+ "ro" => load_locale("ro"),
"ru" => load_locale("ru"),
"tr" => load_locale("tr"),
"uk" => load_locale("uk"),
@@ -3852,7 +3853,7 @@ get "/api/v1/captions/:id" do |env|
caption = caption[0]
end
- url = "#{caption.baseUrl}&tlang=#{tlang}"
+ url = URI.parse("#{caption.baseUrl}&tlang=#{tlang}").full_path
# Auto-generated captions often have cues that aren't aligned properly with the video,
# as well as some other markup that makes it cumbersome, so we try to fix that here
@@ -4523,9 +4524,10 @@ get "/api/v1/search/suggestions" do |env|
query ||= ""
begin
- response = QUIC::Client.get(
- "https://suggestqueries.google.com/complete/search?hl=en&gl=#{region}&client=youtube&ds=yt&q=#{URI.encode_www_form(query)}&callback=suggestCallback"
- ).body
+ client = QUIC::Client.new("suggestqueries.google.com")
+ client.family = CONFIG.force_resolve || Socket::Family::INET
+ client.family = Socket::Family::INET if client.family == Socket::Family::UNSPEC
+ response = client.get("/complete/search?hl=en&gl=#{region}&client=youtube&ds=yt&q=#{URI.encode_www_form(query)}&callback=suggestCallback").body
body = response[35..-2]
body = JSON.parse(body).as_a
diff --git a/src/invidious/helpers/jobs.cr b/src/invidious/helpers/jobs.cr
index 5838b5b3..f368d6df 100644
--- a/src/invidious/helpers/jobs.cr
+++ b/src/invidious/helpers/jobs.cr
@@ -290,6 +290,60 @@ def bypass_captcha(captcha_key, logger)
response = YT_POOL.client &.post("/das_captcha", headers, form: inputs)
yield response.cookies.select { |cookie| cookie.name != "PREF" }
+ elsif response.headers["Location"]?.try &.includes?("/sorry/index")
+ location = response.headers["Location"].try { |u| URI.parse(u) }
+ client = QUIC::Client.new(location.host.not_nil!)
+ response = client.get(location.full_path)
+
+ html = XML.parse_html(response.body)
+ form = html.xpath_node(%(//form[@action="index"])).not_nil!
+ site_key = form.xpath_node(%(.//div[@class="g-recaptcha"])).try &.["data-sitekey"]
+
+ inputs = {} of String => String
+ form.xpath_nodes(%(.//input[@name])).map do |node|
+ inputs[node["name"]] = node["value"]
+ end
+
+ response = JSON.parse(HTTP::Client.post("https://api.anti-captcha.com/createTask", body: {
+ "clientKey" => CONFIG.captcha_key,
+ "task" => {
+ "type" => "NoCaptchaTaskProxyless",
+ "websiteURL" => location.to_s,
+ "websiteKey" => site_key,
+ },
+ }.to_json).body)
+
+ if response["error"]?
+ raise response["error"].as_s
+ end
+
+ task_id = response["taskId"].as_i
+
+ loop do
+ sleep 10.seconds
+
+ response = JSON.parse(HTTP::Client.post("https://api.anti-captcha.com/getTaskResult", body: {
+ "clientKey" => CONFIG.captcha_key,
+ "taskId" => task_id,
+ }.to_json).body)
+
+ if response["status"]?.try &.== "ready"
+ break
+ elsif response["errorId"]?.try &.as_i != 0
+ raise response["errorDescription"].as_s
+ end
+ end
+
+ inputs["g-recaptcha-response"] = response["solution"]["gRecaptchaResponse"].as_s
+ client.close
+ client = QUIC::Client.new("www.google.com")
+ response = client.post(location.full_path, form: inputs)
+ headers = HTTP::Headers{
+ "Cookie" => URI.parse(response.headers["location"]).query_params["google_abuse"].split(";")[0],
+ }
+ cookies = HTTP::Cookies.from_headers(headers)
+
+ yield cookies
end
rescue ex
logger.puts("Exception: #{ex.message}")
diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr
index 53c18dd5..6fcfa8d2 100644
--- a/src/invidious/helpers/utils.cr
+++ b/src/invidious/helpers/utils.cr
@@ -31,8 +31,10 @@ struct QUICPool
begin
response = yield conn
rescue ex
- conn.destroy_engine
+ conn.close
conn = QUIC::Client.new(url)
+ conn.family = (url.host == "www.youtube.com") ? CONFIG.force_resolve : Socket::Family::INET
+ conn.family = Socket::Family::INET if conn.family == Socket::Family::UNSPEC
conn.before_request { |r| add_yt_headers(r) } if url.host == "www.youtube.com"
response = yield conn
ensure
@@ -45,9 +47,11 @@ struct QUICPool
private def build_pool
ConnectionPool(QUIC::Client).new(capacity: capacity, timeout: timeout) do
- client = QUIC::Client.new(url)
- client.before_request { |r| add_yt_headers(r) } if url.host == "www.youtube.com"
- client
+ conn = QUIC::Client.new(url)
+ conn.family = (url.host == "www.youtube.com") ? CONFIG.force_resolve : Socket::Family::INET
+ conn.family = Socket::Family::INET if conn.family == Socket::Family::UNSPEC
+ conn.before_request { |r| add_yt_headers(r) } if url.host == "www.youtube.com"
+ conn
end
end
end