Rubys Twitter-Gem: Rate Limit exceeded vermeiden
Wenn man mit dem Twitter-gem sucht, ist es einfach in das Rate-Limit zu rennen. Nicht nur, dass man sich darum selbst kümmern muss nicht zu viele Abfragen zu starten: Selbst dann noch löst ein Suchvorgang viele Requests auf einmal aus.
Twitters API nutzt cursoring, das heißt sie gibt dir erstmal nur 100 Ergebnisse und dann einen Verweis auf die nächste Seite, auf der wieder 100 Ergebnisse stehen können, und so weiter. Jeder Abruf einer Seite ist ein Request, und der Twitter-Gem folgt diesem Cursor automatisch:
# @return [Enumerator] def each(start = 0) return to_enum(:each, start) unless block_given? Array(@collection[start..-1]).each do |element| yield(element) end unless last? start = [@collection.size, start].max fetch_next_page # hier each(start, &Proc.new) end self end
Leider gibt es im Gem keinen Parameter um das zu vermeiden. Also musste ich den Code selbst anpassen: In lib/twitter/search_results.rb wird aus
# @return [Boolean] def last? !next_page? end
ein
# @return [Boolean] def last? true end
Dann wird zumindest bei Suchabfragen keinem Cursor gefolgt. Das begrenzt dann auch die Suchergebnismenge auf 100, aber das ist in meinem Anwendungsfall okay.
The Kitchen, offensiv schlecht
In The Kitchen führen drei Frauen die kriminellen Geschäfte ihrer Männer weiter, nachdem diese nach einem Raubüberfall ins Gefängnis wandern. Mehr noch, sie übernehmen die Kontrolle der irischen Mafia in Hell's Kitchen, schmieden Allianzen, müssen Gewalt einsetzen und finden ihre Rollen zueinander.
Und all das könnte ein guter Film sein. Stattdessen ist es leider nur eine Ansammlung von Klischees, gefangen in einer hanebüchenen Story. Auffallend auch, wie stark der Film daran scheitert die Entwicklung der Frauen zu zeichnen. Von unterwürfigen Hausfrauen werden sie in einer etwa dreiminütigen Sequenz zu durchgestylten Mafiosi, die problemlos Schutzgelder erpressen, mitleidslos morden und morden lassen und sich dann durch die Mafia wälzen. Dabei wäre genau hier die Story, der langsame Weg vom angepassten Bürger zum skrupellosen Gangster das eigentlich interessante. Stattdessen wird Krise um Krise mit Morden gelöst.
Wobei das nicht heißen soll, dass man die drei in tollen Actionsequenzen sieht. Oder in sonstigen Gangsterszenen. Oder dass sie sonst etwas machen. Nein, nachdem sie am Anfang einmal Geld eingesammelt haben wird über alles weitere nur geredet. Die Action passiert ebenso wie die Charakterverwandlung in kurzen Sequenzen. Im eigentlichen Film reden die Frauen nur über vermeintliche Gangstertaten, und andere reden darüber was die Frauen alles gemacht haben oder reagieren auf sie als seien sie nun Mafiosi. Aber ohne dass man davon irgendetwas sieht, mit wenigen Ausnahmen.
Hat der Film so wirklich gar nichts? Doch, da sind ein-zwei gut gemachte Szenen dabei und die Schauspieler sind nicht verkehrt. Elisabeth Moss ist sogar fast toll, auch wenn ihr Charakter manche Aspekte ihrer typischen Rollen seit Mad Man recycelt, aber wenigstens ist da ein Charakter. Aber ansonsten ist The Kitchen eben offensiv schlecht, wie ein Kinobegleiter richtig bemerkte.
Serendipity 2.3.1
Mit 2.3.1 gibt es ein gar nicht so ganz kleines Patch-Release kurz nach der Veröffentlichung von 2.3.0. Es sind vor allem Schönheitskorrekturen wie das nun wieder mögliche Löschen mehrerer Mediendatenbankeinträge auf einmal. Gerade deswegen ist ein höchsterfreuliches Release, das Thomas in seinem Blog zusammen mit weiteren Entwicklungen näher beschreibt.
Verschwörungen und Epstein
Den Tod von Epstein nutzt die Zeit, um gegen Verschwörungstheorien zu wettern. Der Autor Skudlarek vergisst dabei jedoch, dass es manchmal einfach angemessen ist, einen Verdacht zu hegen und eine Missachtung des regulären Ablaufs anzunehmen.
Um das erst einmal klarzumachen: Wer jetzt ohne weitere Fakten brüllt "Der da hat Epstein deswegen ermordet" ist ein Verschwörungsheoretiker, und ein besonders blöder noch dazu. So wie Trump. Und doch: Sich auf die Grundannahme zu stellen, dass da doch höchstwahrscheinlich etwas nicht mit rechten Dingen abgelaufen ist, das ist richtig.
Epstein war nunmal suizidgefährdet und sollte unter Beobachtung stehen. Das schließt mit ein, in einer Zelle zu sein in der Selbstmord sehr schwierig ist, dazu kommen regelmäßige Kontrollgänge, ein halbstündiges in die Zelle schauen. So berichtet auch die Zeit und das verlinkt Skudlarek sogar. Und das passt einfach nicht zusammen mit seinem Tod. Entweder hat da jemand geschlampt, oder jemand hat dafür gesorgt das geschlampt wird, oder jemand hat Epstein direkt umgebracht. Wir wissen es schlichtweg nicht. Aber wir können uns durchaus überlegen, was hier wahrscheinlich ist, wovon wir ausgehen, welchen Informationen wir in dieser Situation vertrauen sollten. Da spielen dann Überlegungen wie "Wer hatte welches Interesse an diesem Geschehen" einfach rein. Das ist auf einer gewissen Ebene vielleicht Spekulation, aber andererseits ist es genau das was jeder immer machen muss um Informationen zu bewerten.
Und das ist berechtigt. Das ist richtig. Es ist überlebensnotwendig. Wir machen es die ganze Zeit: Wenn jemand etwas vielleicht nur deswegen vertritt, um sich selbst zu schützen, ziehen wir seine Aussage erstmal in Zweifel. Und prüfen doppelt. Wenn eine Partei über ihre Regierungsbilanz berichtet orientieren wir uns lieber an Experten, um die tatsächliche Situation einschätzen zu können. Wenn Michael Roth Waffenlieferungen nach Mexiko verteidigt und offensichlich keine Ahnung hat von der politischen Situation in dem Land, dann glauben wir ihm seine Argumente nicht. Das ist dann nicht der kritische Geist eines Verschwörungstheoretikers, sondern einfach nur tatsächlich kritisches Denken. Das ganz normale Abwägen von Informationen.
Und genau so läuft es auch bei Epstein. Fast jedem der sich die Sache anschaut wird klar: Es ist wahrscheinlich, dass einflussreiche Leute an Epsteins Tod Interesse hatten. Das ist die simple Grundlage der dann weitergehenden, absurden Verschwörungstheorien. Aber diese Grundlage ist eben da. Sie nicht wahrzunehmen und zu sagen "Lass das mal die US-Ermittlungsbehörden machen, denen glauben wir dann vorbehaltlos" – das ist genauso eine Verschwörungstheorie. Die völlig unberechtigte Theorie – bar jeder Grundlage, wider dem was wir über die USA wissen – dass man diesem Regime vertrauen könne aufgrund des Wirkens irgendwelcher höheren Mächte. Dass in dieser gescheiterten Demokratie solch eine Situation glaubwürdig aufgeklärt werden würde.
Warum sollte diese Position in irgendeiner Form intelligenter sein als sich einen Schuldigen zu überlegen und sich darauf festzulegen? Es ist es nicht, beides ist bescheuert. Nur dass Skudlarek nicht merkt, dass genauso wie die Verschwörungstheoretiker von Fakten nicht erreicht werden können, das Ausschalten des eigenen Denkens anfällig zum Falschwahrnehmen der Realität macht.
Es gibt in jeder Situation eine Grundannahme eines jeden. Bei mir in dieser: Der wurde ermordet. Das weiß ich natürlich nicht mit irgendeiner Form von Gewissheit, aber gegeben was über den Fall bekannt ist halte ich es für am wahrscheinlichsten. Jetzt werden die Folgeberichte und was die Ermittlungsbehörden verlauten lassen mich entweder von dieser Position abbringen oder nicht. Aber erstmal stehe ich hier.
Und bin damit kein Spinner – sondern Skudlarek und die Zeit ist unberechtigt unkritisch.
Serendipity 2.3
Die neue stabile Version 2.3 von Serendipity war nötig. PHP bricht einfach immer mehr als stabile Grundlage weg, überspitzt ausgedrückt – die Versionen werden weniger lange unterstützt und die neuen haben gravierende Änderungen. Also muss Serendipity angepasst werden und die Version mit diesen Anpassungen auch zeitnah erscheinen. In meinem Kopf ist 2.3 solch eine erzwungene Version. Denn nun kann Serendipity mit PHP 7.2, 7.3 und 7.4 laufen, bevor 7.1 wegbricht.
Andererseits ist das der neuen Version gegenüber gar nicht fair. Denn einige der Änderungen – weniger zur Alpha, sondern zur letzten stabilen Version – sollten ziemlich bedeutsam sein und Serendipity deutlich verbessern. Mehr noch, wenn sie in der Zukunft noch mehr Feinschliff erhalten und sich alles mehr noch zu einem organischen Ganzen zusammenfügt. Wo ich mitspielte meine ich damit unter anderem: Die Galleriefunktion (Interface), responsive images (Feintuning), den Maintenance-Modus (für Upgrades) und den neuen voku/simple-cache (Redis!).
Was auch für das Release spricht ist die Commitliste: Es sind viele Verbesserungen vieler Autoren drin. Unter anderem, jeweils ein Beispiel: Mario hat das Timeline-Theme unter PHP 7.2 repariert, Don Chambers es generell aktualisiert, Thomas hat an mehr Stellen Patches beigesteuert als mir klar war (und nebenbei den Überblick behalten auch für 2.1.x sowie das Release gestemmt!), hannob Sicherheitslücken aufgedeckt, Matthias das HTML der Bildunterschriften modernisiert, Garvin die Überarbeitung der Mediendatenbank fertiggestellt und so überhaupt erst möglich gemacht, Mitch hat den Trackbackfresser gefunden und Stephan Brunker nl2br/nl2p verbessert.
Froscon 2019
HeuteGestern war ich auf der Froscon! War der Anlass eigentlich nur, Dirk über den Weg zu laufen (der am Sonntag einen Workshop über Regexpressions halten wird) besuchte ich doch auch drei Vorträge.
Daniel Fett erklärte viel zu OAuth, argumentierte gegen eine Variante (den implicit Grant) und zeigte generell Sicherheitsprobleme und Lösungen. Supergut vorgetragen und für mich hochspannend, weil das größtenteils auch OIDC und damit das von Portier verwendete Protokoll betrifft. War ein toller Start in die Konferenz.
Oleg Fiksel erklärte was Matrix ist und was sich im letzten Jahr getan hat. Für mich ein Blick über den Tellerrand (und vielleicht etwas für eine Integration in Pipes?) und ein ordentlicher Vortrag, als Nicht-Matrixnutzer für mich nicht ganz so spannend wie die OAuth-Erklärungen. Aber trotzdem nett.
Die Vortragende redete weniger über Pricacy by Design als vielmehr über die Auswirkungen der DSGVO auf Softwareentwicklung im Unternehmen. Ohne gemein sein zu wollen: Das war mir ein bisschen zu unstrukturiert und zu wenig auf die Softwareprojekte anwendbar, an denen ich arbeite. Mit der Verordnung habe ich mich vorher durchaus schon befasst und ich konnte daher hier wenig neues lernen. Erschwerend: Die Publikumsfragen und Diskussionsversuche fand ich teilweise dem Thema nicht angemessen. Aber ist vielleicht auch schwierig bei einem Thema, das als juristisches außerhalb der Komfortzone der Teilnehmer einer IT-Konferenz liegen dürfte und bei dem das Wunschdenken stark ist. Doch so funktionierte der Vortrag für mich leider nicht.
Ansonsten wollte ich noch den Geany-Leuten am Stand Hi sagen und sie für den tollen Editor loben (und vorschlagen, die Funktion mehrere Zeilen auf einmal zu editieren auszubauen), der Platz war aber leider nicht besetzt. Dafür war das Zusammensitzen, Essen und Trinken am Abend ziemlich nett.
Pipes, Stripe und die SCA-Richtlinie
Strong Customer Authentication (SCA) – schon vor einer Weile hatte Stripe vielen Kunden und auch mir geschrieben, dass dadurch Änderungen anstehen. Zukünftig werde das vorgeschrieben sein (schon im September) und daher musste die Stripe-Integration von Pipes angepasst werden. Heute kam noch einmal solch eine sehr warnende Email, ich habe mir das angeschaut und tatsächlich den Bezahlvorgang angepasst.
Aber trotz eigentlich geringen und positiven Entwicklungsaufwands war der Gesamtprozess nicht toll. Stripe verweist auf diese Dokumentation und erklärt dort ganz viel, aber wenig konkretes. Mich interessieren doch nur zwei Dinge:
- Was muss ich tun, damit Nutzer von Pipes weiterhin ein Abonnement abschließen können?
- Muss ich etwas tun, damit bestehende Abonnements weiterhin monatlich/jährlich abgebucht werden?
Das ist für Stripe sicher schwierig im Voraus zu beantworten, weil die Antwort nicht bei jedem Kunden gleich ist. Aber die Dokumentation scheint mir auch nicht auf die Beantwortung dieser Fragen ausgelegt. Damit bin ich etwas unzufrieden.
Wie auch immer, der erste Schritt war in meinem Fall relativ einfach. Pipes nutzt Stripe Checkout. Das ist eine recht komfortable Softwarelösung, ein bisschen Javascript und etwas Servercode und schon hat man dieses vielleicht bekannte Kreditkarteneingabefenster und kann damit bezahlen bzw Zahlungen entgegennehmen. Also wird das automatisch aktualisiert und Checkout-Integratoren müssen nichts machen? Nein! Stattdessen ist das kleine Fenster jetzt die Legacy-Variante, das neue Checkout funktioniert etwas anders, löst aber die Authentifizierung.
Und genau das hätte mir Stripe in der Email oder der SCA-Dokumentation auch direkt sagen können. Stattdessen ist es in der Checkout-Doku versteckt.
Dabei ist die neue Lösung nichtmal schlecht. Sie sieht hübsch aus und es ist weniger eigener Code notwendig.
Es gibt eine vernünftige Wechselanleitung. Aber im Grunde wechselt man erstmal das Javascript aus. Vorher basierte das auf einem Formular:
<form action="/subscribe" method="POST"> <script src="https://checkout.stripe.com/checkout.js" class="stripe-button" data-key="pk_test_..." data-name="pipes.digital" data-description="Regular Plan" data-amount="20" data-currency="eur" > </script> </form>
Jetzt wird eine Funktion aufgerufen:
<script src="https://js.stripe.com/v3"> <button id="checkout-button">Subscribe <script> var stripe = Stripe('pk_test_...'); var checkoutButton = document.querySelector('#checkout-button'); checkoutButton.addEventListener('click', function () { stripe.redirectToCheckout({ items: [{ plan: 'regular_v1', quantity: 1 }], customerEmail: 'customer@example.com', successUrl: 'https://pipes.digital/sub_success', cancelUrl: 'https://pipes.digital/pricing' }); }); </script>
Klar, bei mir liegt das alles in erb-Templates und sieht daher etwas anders aus.
Vorher musste auf dem Server dann noch das Abonnement angelegt werden:
post '/charge' do customer = Stripe::Customer.create( :email => authorized_email, :source => params[:stripeToken] ) subscription = Stripe::Subscription.create( :customer => customer.id, :items => [ { :plan => params[:plan] }, ], ) end
Nun:
Das entfällt völlig! Da das neue Checkout auf eine Seite bei Stripe weiterleitet wird alles dort erledigt.
Trotzdem muss noch auf dem eigenen System darauf reagiert werden, dass der Nutzer etwas bestellt hat. Bei Pipes wird ein Datenbankeintrag angelegt. Dafür gibt es einen Webhook (einen Ort, an dem ein anderer Server einen POST hinsenden kann), der in der Doku wieder gut erklärt wird. Prinzipiell aktiviert man ihn im Stripe-Dashboard und fängt ihn dann so:
# webhook endpoint for stripes post '/webhook' do payload = request.body.read event = nil # Verify webhook signature and extract the event # See https://stripe.com/docs/webhooks/signatures for more information. sig_header = request.env['HTTP_STRIPE_SIGNATURE'] begin event = Stripe::Webhook.construct_event( payload, sig_header, endpoint_secret ) rescue JSON::ParserError => e # Invalid payload warn "invalid webhook payload" status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature warn "invalid webhook signature" status 400 return end # Handle the checkout.session.completed event if event['type'] == 'checkout.session.completed' session = event['data']['object'] # Fulfill the purchase: plan = session['display_items'][0]['plan']['id'] if plan == 'regular_v1' User.new(email: session['client_reference_id']).promoteToRegular(subscription_id: session['subscription']) end end status 200 end
Es gibt natürlich noch viel mehr Events, die man mit diesem Webhook fangen könnte, zum Beispiel wenn Zahlungen fehlschlagen.
Und die zweite Frage? Ob bestehende Abonnements weiterlaufen blieb mit bisher unklar. Es gibt da zwar einen Abschnitt für, aber ob dieses Grandfathering passieren kann hängt wohl davon ab, ob das Abo so erstellt wurde:
Ihr saht den Code oben, ist das bisher passiert? Der verlinkte Beispielcode sagt ja, aber nichts davon wird explizit im Code erledigt.
Meine jetzige Annahme ist, dass beim Erstellen von Abonnements gemäß des alten Checkout-Verfahrens die Zahlungen weiterlaufen sollten. Man müsste aber wohl eigentlich auf Nummer sichergehen und eine Seite bauen, die über die Stripe-API den Kunden die Möglichkeit gibt, die Authentifizerung nachzuholen. Eine so simple Lösung wie Checkout gibt es dafür aber nicht. Im Fall von Pipes würde sich das Abtauchen in die API kaum lohnen, im Fall der Fälle würde ich dann eher die Nutzer bitten, nochmal neu zu abonnieren.
Naja, spätestens im September wird sich das aufklären.
Wenn Witcher 3 richtig toll ist: Die Schlacht von Kaer Morhen
Witcher 3 spiele ich derzeit wieder, um die Erweiterungen zu erleben. Das ist gar nicht so einfach: Es ist schon ein extrem zeitaufwändiges Spiel. Umso besser dann, wenn das Spiel wenigstens gut ist. Und in der Quest Die Schlacht von Kaer Morhen ist es das. Es ist einer der besten Spielemomente überhaupt. Daher sei sie hier näher beschrieben.
Spoilerwarnung: Ich werde den Quest und damit entscheidende Elemente der Geschichte komplett spoilern. Nicht weiterlesen, wenn du Witcher 3 noch nicht gespielt hast und noch spielen willst.
Die Schlacht ist schon von der Handlung ein zentrales Elemente des Spiels. Endlich ist Ciri gefunden, nach vielen Stunden Spielzeit! Sie wird nach Kaer Morhen gebracht, um sie dort vor der sie jagenden Wild Hunt beschützen zu können. Man sollte erwarten, dass dies das Finale des Spiels ist – aber dem ist nicht so. Es ist nur ein Höhepunkt, das echte Finale noch viele Stunden entfernt.
Das tolle ist: Wer in Kaer Morhen an der Seite der gesetzten Figuren kämpft hängt von den Entscheidungen des Spielers ab. Wurde beispielsweise den Crach-Kindern geholfen, kommt der Sohn vorbei. Ist Keira Metz noch am Leben und dem Witcher Geralt wohlgesonnen, wird die Zauberin helfen. Aber das sind alles Nebenquests. Zwar wird der Spieler durch die Hauptquests aktiv auf sie gestoßen, aber es steht ihm völlig frei sie zu ignorieren. Dann kann er sie nur jetzt nicht mehr rekrutieren. Besser wäre nur noch gewesen, wenn es im Stil von Witcher 1 und 2 alternative Mitstreiter gegeben hätte, die sich gegenseitig ausschließen.
Aber das ist vorher. Auch nach der Ankunft Ciris sind in den Gesprächen noch Entscheidungen zu treffen. Sollen lieber Wände repariert oder Waffen ausgegraben werden? Fallen aufgestellt oder Tränke gebraut? Diese Entscheidungen haben einen klaren Einfluss auf die spätere Schlacht. Das versöhnt damit, dass das Spiel beim grundsätzlichen Handlungsverlauf keine Alternative gibt.
Die Inszenierung ist erstklassig. Wenn in der Zwischensequenz der Atem sichtbar wird, weil die Eiseskälte angekommen ist, der aufgehängte Holzbalken im Wind weht, dann ist das richtig spannend. Weil klar wird: Jetzt gibt es endlich die Schlacht, auf die so lange hingearbeitet wurde. Die vorher eher langsam vor sich hinplätschernde Story nimmt endlich an Fahrt auf.
Wichtig dabei, dass die spielbaren Abschnitte nicht weniger spannend sind. Erst vor der Burg, dann in der Burg müssen Gruppen von Feinden ausgeschaltet und Portale geschlossen werden, als Geralt und dann auch als Ciri. Die sich ja durchaus etwas anders steuern. Die Engine zeigt dabei was sie kann: Es sind deutlich mehr Gegner als sonst üblich auf dem Bildschirm, dazu kommen die Begleiter. Sicher, da wurde getrickst um den Effekt zu verstärken ohne an die technischen Grenzen zu kommen – es sind dann doch mehr viele kleinere Gruppen – aber es funktioniert, der Eindruck einer großen Schlacht kommt voll rüber.
Eine Schlacht, die verloren wird. Zu viele Gegner kommen an, die Begleiter sind in Bedrängnis, die Gruppe zieht sich in den Innenhof zurück.
Und dann stirbt Vesemir.
Der alte Mann war in Witcher 1, er begleitet den Spieler am Anfang von Witcher 3, er ist ein wichtiger Aspekt der Bücher. Seine enge Bindung zu Geralt und Ciri wird in der Story klar vermittelt und er ist zu dem Zeitpunkt sicher auch dem Spieler wichtig geworden. Und doch ist er es, der Ciri beschützend im Kampf umkommt.
Das ist emotional einfach schwierig. Und genau deswegen passt es so gut in dieses Spiel. Die Spielereihe wollte immer Konsequenzen zeigen und erwachsene Geschichten erzählen (was sich mit dem Teenager-Fantasiecharakter von Geralt ziemlich beißt). Und hier hatte das Spiel gewarnt: Was die Gruppe diesmal versucht ist wirklich gefährlich. Die Charaktere sind aufgeregt, angespannt, an vielen Stellen wird vor dem Risiko gewarnt. Nun hatte der Spieler selbst keine Wahl. Er konnte die Quest möglichst gut vorbereiten, das ist alles. Aber das reicht eben nicht, um diese Konsequenz der Handlung zu vermeiden. Und damit Motivation für den folgenden Spielverlauf zu geben, in dem die Dynamik der Story umgedreht wird und die Protagonisten in die Initiative gehen.
Spielerisch ist das alles nicht die große Offenbarung. Es gibt Kämpfe und Entscheidungen, während der Schlacht sogar nur noch eine Abfolge von Kämpfen. Aber wie es inszeniert ist, wie es in die Gesamthandlung eingebunden wurde und wie vorherige Handlungen im Spiel hier Auswirkungen haben, all das macht Die Schlacht von Kaer Morhen zu einem ganz großen Moment.
Und es zeigt überdeutlich, wie das Ende von Mass Effect 3 hätte inszeniert sein müssen und wie beschissen es wirklich war.