Die kurzzeitige Rückkehr der YouTube Dislikes – Ein Tauchgang in die Tiefen der YouTube API (und wieder zurück)

Im Dezember 2021 wurde die öffentliche Darstellung der Dislike-Anzahl bei allen YouTube Videos deaktiviert. Die Anzahl der Dislikes wäre nun privat, sagte Google. Es ist wohl verständlich, dass viele diesen Schritt eher als Verschlimmbesserung der Plattform betrachten. Immerhin konnte man so die Qualität der Videos meist zuverlässig einschätzen ohne sich das Video komplett anzuschauen. Als Content Creator gibts da natürlich auch andere Sichtweisen, wie YouTube unter anderem in einem Blogbeitrag beschreibt. Wie dem auch sei, man kann davon ausgehen, dass YouTube diese Änderung nicht wieder rückgängig macht. Aber wie wurde diese Änderung überhaupt aus technischer Sicht durchgeführt? Spoiler: Nicht durchgängig.

Ein Einblick in die Tiefen der YouTube API und Relikte aus vergangenen Zeiten.

Die Motivation zu suchen

Wenn man was finden will muss man ggf. suchen. Aber wonach? In den letzten Monaten hatte ich an einigen Projekten gearbeitet, die sich unter anderem mit dem Download von YouTube Videos als auch das Umgehen von Alters- und Geo-Beschränkungen befassen. Viele der dort genutzten Tricks werden immer wieder seitens YouTube “gepatched”, so dass eigentlich kontinuierlich nachgebessert werden muss. Das betrifft im Grunde alle Projekte die von der internen YouTube API profitieren. Bei meinem kleinen Recherche-Projekt ging es nicht darum die Dislikes wiederzufinden, ich wollte lediglich einen “Trick” finden um auf altersbeschränkte Videos ohne Login zuzugreifen, was momentan offiziell nicht mehr möglich ist.

Die “interne” YouTube API

Neben der öffentlichen YouTube API die über eine ausführliche Dokumentation verfügt, gibt es auch eine API die von der YouTube Website und den Apps für Mobilgeräte genutzt wird. Diese API ist weitestgehend undokumentiert. Ein wesentlicher Vorteil von dieser internen API ist, dass man keine eigenen API Keys generieren muss und dass man viel mehr Informationen erhält als von der YouTube Data API.

In Fachkreisen nennt man die interne YouTube API auch “Innertube API”, da Google diese wohl mal so getauft hat. Um Anfragen an diese API stellen zu können, benötigt man einen API Key, welcher global für die gesamte Website bzw. mobile App gültig ist und ein clientName sowie eine clientVersion. Für die YouTube Website ist der Client Name bspw. WEB und die Version 2.20220405. Je nachdem welchen Client Namen und welche Version man übermittelt, tauchen unterschiedliche Details in der Serverantwort auf, da sich das Response Layout an die Beschaffenheiten des Clients anpasst. So kann man bspw. mit dem Client Namen ANDROID und der Version 17.13.3 weitere Videoformate und Codecs erhalten. Jeder Client hat entsprechende Versionen (entspricht meist der Website- oder mobile App- Version), die bei Anfragen korrekt übermittelt werden müssen. Zudem sind bisher nur wenige Client Namen öffentlich bekannt. Dazu gehören unter anderem die Clients der offiziellen YouTube Apps die bspw. ANDROID, IOS, IOS_MUSIC als Client Namen nutzen sowie die Websites die WEB, MWEB (mobile Seite) und WEB_EMBEDDED_PLAYER (eingebetteter YouTube Player) nutzen. Mit all diesen Clients kann man leider keine altersbeschränkten Videos ohne gültige Session abrufen. Zudem werden viele europäische Nutzer dazu aufgefordert ihren Personalausweis zur Verifizierung hochzuladen, was natürlich kaum jemand machen möchte. Mein Ziel war es, weitere Clients zu finden, die dann vielleicht zur Umgehung dieser Restriktionen genutzt werden können.

Der Ansatz

Zunächst mal ein Blick auf die HTTP Anfrage die zum Abrufen der Videoformate genutzt wird:

POST /youtubei/v1/player?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8 HTTP/2
Host: www.youtube.com

{
  "context": {
    "client": {
      "clientName": "WEB",
      "clientVersion": "2.20220406.09.00"
    }
  },
  "videoId": "Ic1kvKSgmDE"
}

Der Server Antwortet bei normalen Videos mit playabilityStatus:"OK" und bei altersbeschränkten Videos mit playabilityStatus:"LOGIN_REQUIRED". Wenn man nun anstelle "clientName":"WEB" einfach mal "clientName":"ABCDEF" angibt, dann gibts folgende Meldung:

{
  "error": {
    "code": 400,
    "message": "Invalid value at 'context.client.client_name' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.ClientInfo.ClientName), \"ABCDEF\""
   }
}

Man braucht also einen gültigen clientName und eine passende clientVersion. Aber wie kann man gültige Client Namen neben den bereits bekannten finden? Seit geraumer Zeit war mir bekannt, dass Google an allen möglichen Stellen mit Aufzählungstypen (Enums) arbeitet. Dabei kann jeder gültige Client sowohl mit dem Namen (bspw. WEB) als auch mit seiner numerischen ID (bspw. 1) angesprochen werden. Das Feld clientName ist somit numerisch iterierbar.

Die folgende Anfrage liefert das gleiche Ergebnis wie zuvor mit WEB:

{
  "context": {
    "client": {
      "clientName": 1,
      "clientVersion": "2.20220406.09.00"
    }
  },
  "videoId": "Ic1kvKSgmDE"
}

Nun brauchte ich also ein kleines Skript welches alle clientNamen von bspw. 1 bis 100 ausprobiert und diese dabei mit bekannten Client Versionen kreuzt.

Let’s Script

Zunächst erstellte ich zwei Skripte die aus dem Apple App Store und Google Play Store alle Versionen der von Google angebotenen Apps extrahierten. Siehe fetch_android_versions.py und fetch_apple_versions.py. Daraus resultierte inklusive ein paar Handvergebenen Versionsnummern eine Wordlist von 265 Einträgen (siehe client_versions.txt). Zudem testete ich das Ganze an drei verschiedenen (nicht altersbeschränkten) Videotypen, da es durchaus sein kann, dass manche Clients nur für bestimme Videos genutzt werden können. Das wären:

  • Normale Videos (ohne Content-ID claims)
  • Musikvideos (für YouTube Music Clients)
  • “for Kids” Videos (für YouTube Kids Clients)

Summa summarum sinds dann 100 ClientNames x 265 ClientVersions x 3 Videos. Also insgesamt 79.500 Anfragen die mit dem Skript innerbrute.py ausgeführt werden. Erfolgreiche Antworten (mit Statuscode 200) werden direkt in eine Datei geschrieben. Glücklicherweise verfügt die interne API über keine Ratenlimitierung.

Nach einigen Stunden gabs dann zu meiner Verwunderung eine Vielzahl an erfolgreich beantworteten Anfragen. Insgesamt waren es 58 unterschiedliche ClientNames die nun in Form von IDs und passenden Versionen vorlagen. Da nicht alle YouTube Endpunkte den ClientName in Form einer numerischen ID akzeptieren, versuchte ich als nächstes die zugehörigen Strings zu finden. Tatsächlich verrät der Response den echten Client Namen über den c Parameter in der Stream-URL des JSON Response:

http://rr4---sn-4g5e6nss.googlevideo.com/videoplayback?expire=1648456686&ei=jh9BYu7VJ8OI6dsPj9OC8AU&ip=[redacted]&id=o-AFitAIbj1C69t3mag_kDmGMVPofZcu4MIuUF1DeH8uRJ&itag=17&source=youtube&mh=xC&mm=31%2C26&mn=sn-4g5e6nss%2Csn-h0jeln7e&ms=au%2Conr&mv=m&mvi=4&pl=36&gcr=de&initcwndbps=1250000&vprv=1&mime=video%2F3gpp&gir=yes&clen=3041803&dur=320.899&lmt=1622122774929421&mt=1648434766&fvip=4&fexp=24001373%2C24007246&c=MWEB_TIER_2&txp=5432434....

Die Client Name ID 27 entsprach hier dem Client Name String MWEB_TIER_2. Und so gings dann auch bei allen anderen Clients. Somit konnte ich nun eine Liste mit allen funktionierenden Clients und der jeweils höchsten Versionsnummer erstellen: working_clients.txt

Nun war man also im Besitz von einigen bisher unbekannten Clients der internen YouTube API. Aber was kann man damit anfangen? Vor allem Client Namen wie WEB_INTERNAL_ANALYTICS und WEB_UNPLUGGED_OPS klingen irgendwie vielversprechend…

Testen, testen, TESTEN, tEsTeN

Eigentlich gabs nicht sonderlich viel zu testen. Mein Ziel war es ja lediglich einen Client zu finden mit dem man altersbeschränkte Videos ohne Login abfragen kann. Schnell war ein Testvideo gefunden und ich hämmerte die YouTube API erneut mit Anfragen zu. Und siehe da, exakt ein Client erlaubte den Zugriff auf das Video. TVHTML5_SIMPLY_EMBEDDED_PLAYER in der Version 2.0 🥳

Zum Direkten Vergleich:
WEB Client:


TVHTML5_SIMPLY_EMBEDDED_PLAYER Client: ✅


Damit war mein Ziel erreicht. Ich veröffentlichte die Rechercheergebnisse auf GitHub und implementierte den neuen “Trick” in die entsprechenden Projekt. Die eigentliche Überraschung kam erst ein Tag später.

Die vergessenen Response Templates

Wie schon beschrieben, hat jeder Client eine eigene Response Struktur, die sich je nach Version an die Funktionen des User Interface anpasst. Bei dem Endpunkt youtubei/v1/player gab es abgesehen von unterschiedlichen Videoformaten nicht sonderlich interessante Dinge die man durch Variation des clientName und clientVersion erhalten konnte. Anders jedoch bei dem Endpunkt youtubei/v1/next, welcher zum Abruf von zusätzlichen Informationen wie der Videobeschreibung, Kommentaren, Videovorschläge usw. genutzt wird. Hier staunte ich nicht schlecht als plötzlich neben der Likeanzahl auch die exakte Anzahl der Dislikes im Reponse auftauchte. Konkret war das bei folgenden Clients der Fall:

  • ANDROID_TESTSUITE – Version 1.9
  • WEB_INTERNAL_ANALYTICS – Version 0.1
  • TVHTML5_SIMPLY_EMBEDDED_PLAYER – Version 2.0
  • TVLITE – Version 2

Es ist unklar warum es über diese Clients noch möglich war an die Dislikes zu kommen. Ich vermute, dass es sich bei diesen Clients um “Legacy” Versionen handelt, die nicht mehr aktiv weiterentwickelt werden und vielleicht auch gar nicht mehr in Benutzung sind. So hat Google vielleicht schlichtweg die Anpassung des Reponse Layouts verpennt oder nicht für nötig gehalten. Da die öffentliche Darstellung der Dislikes zu diesem Zeitpunkt schon vor mehr als 4 Monaten offiziell abgeschafft wurde und bisher keine Tricks bekannt waren um dennoch an die exakte Anzahl der Dislikes zu kommen, entschied ich mich diesen Fund der Open Source Community mitzuteilen. Vielleicht kann ja das Team von Return YouTube Dislike diesen Trick in die Browser Erweiterung einbauen, dachte ich mir. Diese sehr populäre Browser Erweiterung nutzt archivierte Daten sowie das Verhältnis zwischen Aufruf- und Like-Anzahl um eine ungefähre Anzahl der Dislikes zu berechnen.

Ein gut gemeinter Hinweis

Also erstellte ich ein Feature Request in dem Return YouTube Dislike Repository und wies auf den Trick hin mit dem man die echte Anzahl der Dislikes erhält.

Einige Nutzer kommentierten das Issue bis es dann nach ca. 15 Minuten kommentarlos vom Projektbesitzer gelöscht wurde.

Anscheinend fand da jemand den Trick doch nicht so gut. Aber warum?

Um die Sache Geheim zu halten war es nun aber schon zu spät. Die Information verbreitete sich rasend schnell auf dem Discord Server von Return YouTube Dislike. Auch da versuchte jemand sämtliche Kommentare zu dem Thema zu entfernen. Den Grund für die Löschung des Issues verriet mir schlussendlich der Betreiber. Man wolle sowas nicht öffentlich machen, da Google es sonst fixt. Generell hat er damit ja recht, aber was nutzt dieser Trick wenn ihn niemand kennt? Ein paar User programmierten in den Folgetagen eigene Browser Extensions und Userscripts um den Dislike Count über diese Methode zu ermitteln. Leider hat Google tatsächlich recht schnell nachgebessert und den Trick gefixt. Ob eine Korrektur der Response Layouts nun aufgrund der Veröffentlichung des Tricks geschehen ist oder ob eine Anpassung ohnehin geplant war, ist fraglich.

Ich fand die ganze Sache jedenfalls ziemlich interessant 🙂

tl;dr

Durch die Iteration und Kreuzung unterschiedlicher Client Namen und Versionen konnten einige bisher unbekannte YouTube Clients gefunden werden. Einer dieser Clients kann zur Umgehung der Login Pflicht für altersbeschränkte Videos genutzt werden (stand 12.05.2022). Bei einigen anderen Clients hatte YouTube vergessen die Dislikes aus dem Response Layout zu entfernen. So konnte man bis zum 05.04.2022 die exakte Anzahl der Dislikes von jedem beliebigen Video ermitteln. Hier wurde bereits nach einigen Tagen nachgebessert.

Timeline

  • 29.03.22 – Entdeckung der Dislikes im youtubei/v1/next Response bei Nutzung des TVLITE Clients
  • 29.03.22 – Issue auf GitHub aufgemacht und den Dislike Trick veröffentlicht
  • 02.04.22 – YouTube entfernt dislikesCount aus TVLITE Client
  • 05.04.22 – YouTube entfernt dislikesCount aus den restlichen Clients

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.