<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Hardly</title>
  <link href="https://hardly.link/posts/feed.xml" rel="self"/>
  <link href="https://hardly.link/"/>
  <updated>2023-07-19T07:02:17+00:00</updated>
  <id>https://hardly.link/</id>
  <author>
    <name>Kees</name>
  </author>
  <entry>
    <id>https://hardly.link/posts/vogue-letting-me-down.html</id>
    <link href="https://hardly.link/posts/vogue-letting-me-down.html"/>
    <title>Vogue letting me down</title>
    <updated>2023-07-19T01:00:00-07:00</updated>
    <content type="html"><![CDATA[<p>I was trying to do something like this a while ago…</p><p>Need all high-resolution images from a fashion collection on Vogue’s often-paywalled slideshow viewer.</p><h2 id="deps.edn">deps.edn</h2><pre><code class="lang-clojure">{:paths &#91;&quot;script&quot;&#93;
 :deps {etaoin/etaoin {:mvn/version &quot;1.0.40&quot;}}}
</code></pre><h2 id="script/dl.clj">script/dl.clj</h2><pre><code class="lang-clojure">&#40;ns dl
  &#40;:require &#91;etaoin.api :as e&#93;&#41;&#41;

&#40;defn grab
  &#91;url&#93;
  &#40;e/with-safari d
    &#40;e/go d url&#41;
    &#40;e/wait-exists
     d {:css &quot;ul li picture.responsive-image &gt; source&quot;}&#41;
    &#40;loop
     &#91;links &#91;&#93;&#93;
      &#40;let &#91;img &#40;e/get-element-attr
                 d {:css &quot;ul li picture.responsive-image img&quot;}
                 &quot;src&quot;&#41;&#93;
        &#40;if &#40;= &quot;disabled&quot; &#40;e/get-element-attr
                           d {:css &quot;div&#91;data-testid='RunwayGalleryControlNext'&#93; &gt; svg&quot;}
                           &quot;arrowdisabled&quot;&#41;&#41;
          &#40;conj links img&#41;
          &#40;do
            &#40;when &#40;e/visible?
                   d {:css &quot;.persistent-bottom button&#91;aria-label='Collapse'&#93;&quot;}&#41;
              &#40;e/click
               d {:css &quot;.persistent-bottom button&#91;aria-label='Collapse'&#93;&quot;}&#41;
              &#40;e/wait d 1&#41;&#41;
            &#40;e/click
             d {:css &quot;div&#91;data-testid='RunwayGalleryControlNext'&#93;&quot;}&#41;
            &#40;e/wait d 1&#41;
            &#40;recur &#40;conj links img&#41;&#41;&#41;&#41;&#41;&#41;&#41;&#41;
</code></pre><h2 id="then">Then</h2><pre><code class="lang-clojure">&#40;def links
  &#40;dl/grab &quot;https://www.vogue.com/fashion-shows/spring-1993-ready-to-wear/john-galliano/slideshow/collection&quot;&#41;&#41;

&#40;doseq &#91;link links&#93;
  &#40;spit &quot;ss-93-rtw.txt&quot; &#40;format &quot;%s\n&quot; link&#41; :append true&#41;&#41;
</code></pre><pre><code class="lang-bash">while read p; do curl &quot;${p}&quot; -so &quot;ss-93-rtw/${p##&#42;/}&quot;; done &lt; ss-93-rtw.txt
</code></pre><p>Sorry Vogue..</p>]]></content>
  </entry>
  <entry>
    <id>https://hardly.link/posts/letter-to-my-sister-2.html</id>
    <link href="https://hardly.link/posts/letter-to-my-sister-2.html"/>
    <title>A letter to my sister</title>
    <updated>2023-06-10T01:00:00-07:00</updated>
    <content type="html"><![CDATA[<p>Global war breaking out I’m doing doordash for some reason and really fuck up an order to LAX. And I’m with you. I order for myself too from a mexican restaurant and try to pick up both myself while the employees are on break. They see me and get mad and don’t let me take it without meeting me in the bosses’ office. I sneak around and grab them and on the way to LAX a major conflict breaks out and the gov police state starts beating / tasing / shooting and warding people with kind of a star wars electrocuting vibe. I abandon the doordash order and go off with you somewhere. We are then on foot somehow and then go to mainland china as war refugees and now global war is tearing apart every corner of civilization because of the LA sci fi style military police riot. We are on the run and in hiding alone. We’re looking for food and shelter and take what we can. You’re amazing and finds things like abandoned food orders and cleaning products. I go up a mile high tree to look for resources while we both “fish.” You find fish and I am kind of dawdling in this tree saying we can make it our shelter. She’s doing so much better and stockpiling resources in a bathtub in a just-abandoned home. You and the presence of out family still have morals and guilt for ransacking life but I convince them to act meaninglessly in pursuit of their survival because of my moral fall from grace starting with the doordashing theft and culminating in my willingness to raid and kill. You have a phone and internet for some reason and want to give up and take a plane to somewhere you perceive to not be as affected by the war and where you have an easier chance of culturally adjusting and succeeding. I think alone. You want to connect from khmer to some final mysterious country I can’t remember.</p>]]></content>
  </entry>
  <entry>
    <id>https://hardly.link/posts/jdownloader-letting-me-down.html</id>
    <link href="https://hardly.link/posts/jdownloader-letting-me-down.html"/>
    <title>jDownloader letting me down</title>
    <updated>2023-03-07T19:00:00-08:00</updated>
    <content type="html"><![CDATA[<p>Zippy not working… Back in the trenches with Etaoin</p><h2 id="deps.edn">deps.edn</h2><pre><code class="lang-clojure">{:paths &#91;&quot;script&quot;&#93;
 :deps {etaoin/etaoin {:mvn/version &quot;1.0.39&quot;}}
 :aliases {:grab {:exec-fn dl/grab}}}
</code></pre><h2 id="script/dl.clj">script/dl.clj</h2><pre><code class="lang-clojure">&#40;ns dl
  &#40;:require &#91;etaoin.api :as e&#93;&#41;&#41;

&#40;def links
  &#91;&quot;https://www28.zippyshare.com/v/xxxxxx/file.html&quot;
   &quot;https://www11.zippyshare.com/v/xxxxxx/file.html&quot;
   ...&#93;&#41;

&#40;defn grab
  &#91;&amp; &#95;&#93;
  &#40;e/with-chrome-headless
    d
    &#40;doseq &#91;link links&#93;
      &#40;e/go d link&#41;
      &#40;e/wait d 1&#41;
      &#40;e/click d {:css &quot;#dlbutton&quot;}&#41;
      &#40;e/wait d 30&#41;&#41;&#41;&#41;

</code></pre><h2 id="on&#95;the&#95;command&#95;line">On the command line</h2><pre><code class="lang-sh">clj -X:grab
</code></pre><p>Shook that works</p>]]></content>
  </entry>
  <entry>
    <id>https://hardly.link/posts/black-beard.html</id>
    <link href="https://hardly.link/posts/black-beard.html"/>
    <title>Black-beard</title>
    <updated>2023-02-04T15:30:00-08:00</updated>
    <content type="html"><![CDATA[<p>2022-09-02</p><p>Black-beard in the springtime crushed roses in his teeth for hard luck on enemies through the year. His memoir indicated this idea came from vagrants on the way to Mendocino from coastal B.C. in the 1970s, whose profession—wild leather—had brought them around. The pair were too open about distinctly confused Indic beliefs and a warlike approach to their craft. This clashed with locals, and did not lead to success. The cows were safe but in time some things drug into the stocky and greyed man’s brain. Black-beard’s disposition lent him little during purchases of cattle supplies in town, every time arriving darkened at feed shops, let alone the big annual market offing, his reputation starting unpopular and continuing unshaken from its pit 25 years later. In summer 1988 he burned his entire farm down claiming betrayal by a vague mistress, stole an acquaintance’s boat and launched off the Eureka coastline as a pirate. He felt unusual and troubled in town and this did not pass on the water. Like dry business piracy is difficult to undertake crewless.</p><p>Modern cargo shipping has natural crime resistance outside the dangerous areas of the world with the impermeable shells of its massive freighters, and seabound sightings of crown luxury appropriate for looting are uncommon on the northern Pacific coast even today. Small-time yachtsmen generally do not carry precious assets on short day trips out of local yacht clubs, except maybe a few rare bottles of spirits or sparkling wine.</p><p>Black-beard’s rule was arbitrary. Intuition only gets people so far and should often fail. He dredged old destinations from stories and days past to chart his new life. Sometimes picking up much younger people, unsure if they were mates or passengers, receiving their suspicions toward his way of being with cracks in his disciplined performance, he started to fade. They looked at him chewing something at the stern.</p><p>Less and less was heard about the local legend, maybe more an anecdote, until one day young people at the shore encountered a small beached craft broken opposite large rocks, carrying a peaceful, sunken seafaring man smiling at the low sky. His hand closed around wiry saltgrass like a bouquet and he passed from life satisfied. A group of local Californians gathered for Black-beard’s wake. Some even drove from areas of Oregon to attend. No one arrived from as far north as Portland, but Black-beard the saved sea criminal had apparently held a small sway in agricultural pocket suburbs up the coast in the later 20th century.</p><p>Flaunt magazine</p>]]></content>
  </entry>
  <entry>
    <id>https://hardly.link/posts/letter-to-my-sister.html</id>
    <link href="https://hardly.link/posts/letter-to-my-sister.html"/>
    <title>Letter to my sister</title>
    <updated>2023-01-14T07:40:00-08:00</updated>
    <content type="html"><![CDATA[<p>Dreamed that our whole family was going to a giant annual perfume expo where hundreds of young perfumers brought their new formulas to hopefully get discovered by buyers. It was in the future and because we were just random people we could only preview the smells with the scent emitter feature on phones. You didn’t know who made which one because the buyers were supposed to be unbiased and smell blind. Kanye West was there for some reason like the keynote speaker MC. We couldn’t spend a long time there because it was a long drive and late at night so I was quickly trying to go through as many scents as I could on the phone to find what I liked. All the perfumers were sitting at different tables sorted by letter of alphabet. The tables had art deco central podiums like carousels. My favorite scent was called “Dragon An Hunted An” so because of the last word they were at the A table. There were a lot of really weak and amateur scents cus a lot of these people were just graduating from perfume school but even on the phone you could smell this one was very delicate, complex, and intriguing. We had to get ready to go… And you STOLE THE REAL BOTTLE out of the freezer at table A!!!! They brought such concentrated small quantities that all the real perfumes were just frozen in large double-wall glass bottles to be diluted. (Not true to life no reason to freeze.) I was so mad. You wanted me to smell it irl before we left and said you would put it back. You just mixed a tiny bit with water which did NOT do a good job diluting it so it wasn’t even worth it. And, even more so, on the bottle it was clear this specific perfume was made by cool and hip trans people. Where a barcode would be said ‘T4T’. All the bottles looked really cool, especially frosted out of the freezer. The hosts immediately found out you did this and we got kicked out and banned from the entire expo, and everyone hated us. THANKS A LOT!!!</p>]]></content>
  </entry>
  <entry>
    <id>https://hardly.link/posts/readme-md.html</id>
    <link href="https://hardly.link/posts/readme-md.html"/>
    <title>Readme.md</title>
    <updated>2023-01-14T00:00:00-08:00</updated>
    <content type="html"><![CDATA[<p>Posted on <a href='https://github.com/kees-/kees-'>github profile</a>. About my beliefs in software:</p><p>I'm a hobbyist programmer, love to learn and improve. I've gravitated toward small tools that deliver unique utility, then bury that utility in flavor and inconsequential content.</p><p>Most software, pretty or not, lacks a voice. This is an absolute fact. Across many domains, big or small, consequential software (on the desktop, websites, mobile apps, tools, engines, firmware, networks, services) cleave functionality from personality.</p><p>Personality includes visual decoration, but goes further. A domain you tend to see personality in is game design. That makes sense, because video games are: interactive processes where meaningless actions are wrapped in arbitrary content, imbuing actions with ideas to entertain, provoke thought, and provoke emotion.</p><p>Video games are ultimately distractionware—nothing against Terry Cavanagh!—they're fun, but I said it above, their basic mechanical actions are meaningless. I’m not that interested in making games.</p><p>In the history of digital design, I see the span of time from the dotcom boom to the pandemic being the rock bottom of craft at the modern forefront of utility objects. Design makes me think of a table and chairs, or an article of clothing. Like with mass manufacturing, handicraft is lost in the pursuit of practical software design.</p><p>People do love personal websites. That has been a notable location of personality for decades. I am not making a case that solutions are getting carpal tunnel from handwriting html, or limiting your design skills to figma and webflow. Branded experiences may have personality, but are made hollow by their aspects all needing to serve commerce. Toys and visualizations are a middle ground, a case can be made for meaning in their utility. I do think that beyond these instances, I've started to have my interests piqued in recent years. In the weird browser I currently use, copying the URL makes blue cubes float around the corner of the screen. Why?! Decor. Now, become personal.</p><p>My belief that inspires me to do code, is that personality and utility should be separate and always consuming each other. Boundaries on usefulness don't inhibit creativity.</p><p>Personality in software should constrain the user, and press on the user. It should mean decoration, and obfuscation, and where text is present, it should create a conversation with the user. Software should fade and die. Most importantly (as I see it, because it's what I see the least of), the subjects of software personalities shouldn't have to couple with the content of their utility. My favorite part of personality is the oblique.</p>]]></content>
  </entry>
  <entry>
    <id>https://hardly.link/posts/downloading-mid-pop-culture.html</id>
    <link href="https://hardly.link/posts/downloading-mid-pop-culture.html"/>
    <title>Downloading mid essays about pop culture</title>
    <updated>2023-01-01T00:00:00-08:00</updated>
    <content type="html"><![CDATA[<blockquote><p> Yess…<em>avatar</em>…indigenous blue man group </p><p> — <em>The Way of Water</em> </p></blockquote><p>My idea of fun is compulsively downloading stuff…</p><p>I pared back <a href='https://github.com/pkonkol/substack-to-pdf'>an impromptu script</a> recently that archives the entirety of a substack blog as a standardized data format (ePub via JSON). What if I could expand my domain and download from different publications as well?</p><p>Thanks for reading!</p><h2 id="ripping&#95;a&#95;new&#95;source">Ripping a new source</h2><p>I tried pulling articles from <a href='https://spikeartmagazine.com'>spikeartmagazine.com</a> the same way that <a href='https://github.com/kees-/substack-to-json'>substack-to-json</a> does, by just changing what elements it looks for. Well, surprise, after an hour or two it worked fine! It automatically batch downloaded everything from a specific contributor (probably also works with subjects and categories). But writing python gets old fast…</p><p>I learned more about webdrivers on the way. Chiefly, that <code class="lang-none">etaoin</code> is a perfectly good webdriver library in clojure. With a little work, the python <code class="lang-none">substack-to-json</code> can be adapted—then later, generalized.</p><p>Why did I want to do this? Lol.. <a href='https://www.spikeartmagazine.com/?q=contributors/dean-kissick'>dean-kissick</a> <a href='https://www.spikeartmagazine.com/?q=contributors/dean-kissick-0'>dean-kissick-0</a> I just wanted this guy’s articles. He made a good playlist, gets referenced in the vibes scene, and now I trust him. I demonstrate my trust by repossessing your content, in some grey area between archiving and piracy.</p><h3 id="compare&#95;and&#95;contrast">Compare and contrast</h3><p><code class="lang-none">substack-to-json</code>, the technique is: access an index page and crawl through to find every relevant URL, then iterate those URLs to grab fully rendered page content. How general is this method on other written content?</p><p>Caveats certainly vary across platforms, like to go through an entire substack archive you need to simulate scrolling to the bottom; on Spike everything loads at once.</p><p>Styling static pages once articles are rehosted is another story. Substack is fine, just put classes on a few tags and include their universal CSS file, and posts look as good or better than on their origin. It was kind of magic. Spike styles were a horrifying thicket of jQuery and Drupal, and their resources are CORS-blocked. Other than just being better engineered, Substack may enable cross-origin requests due to being a platform for serial content hosted on custom domains, rather than a single outlet? I don’t know what situations warrant one config or the other.</p><h3 id="webdrivers">Webdrivers</h3><p>So, webdrivers seem absurd and testament to how inverted the web is. You are creating a temporary engine that simulates an entire browser, invisibly or even visibly, and allows ‘RPC’-style calls to interact with DOM minutia in the most granular way. Much more control than I anticipated is possible, down to filling and submitting individual form elements, individual keypresses and clicks, etc, that is scratching the surface. Web is bizarre. Robots imitate human access patterns, humans are conformed to be data robots, and the entire ecosystem has become content farms.</p><p>But, in my vague first tests playing with <code class="lang-none">etaoin</code>, I am having a difficult time understanding how to adapt to a basic functional style. It’s giving imperative. It seems like interaction with a webdriver leans in to mimicking an actual web surfing experience, the ‘<code class="lang-none">driver</code>’ you reference cloaks a giant mutable browser runtime that is one giant side-effecting entity.</p><h3 id="other&#95;things">Other things</h3><p>I paid attention to and leveraged head metadata tags for the first time. Instead of throwing myself into the trenches of body content, much of the relevant information, like post dates, authors, bylines, all exist sanitized and isolated in the heads of pages! Who knew lol. (Every single web dev down to the entry level)</p><p>I had been using XPath as that’s what the <code class="lang-none">substack-to-json</code> author was using. Overkill! Looks like webdriver software generally allows the user to query the DOM in whatever way is most convenient, like.. CSS selector chains. Easier, more readable, basically just as flexible! I would rather avoid yet another DSL and reuse a structure-referencing syntax I already know.</p><h2 id="trying&#95;a&#95;clj&#95;webdriver">Trying a clj webdriver</h2><p><code class="lang-none">etaoin</code>, referenced above, is a pleasant surprise after I got a little mystified in python. I know enough python to adapt and hack on existing scripts to fit my use cases, but joy sparks are not exactly what float through my amygdala.</p><p>I will adapt each individual action I’d taken to clojure. It seems like the original script I’ve used,</p><h3 id="starts&#95;a&#95;headless&#95;(invisible)&#95;browser&#95;instance:">Starts a headless (invisible) browser instance:</h3><pre><code class="lang-clojure">&#40;require '&#91;etaoin.api :as e&#93;&#41;

&#40;e/with-chrome-headless
 {:path-browser &quot;...&quot;} ;; As, woops, I didn't have chrome installed
 driver ;; A binding, not a reference! Gotcha
 ;; Now, all subsequent forms are executed declaratively
 &#41;
</code></pre><p><code class="lang-none">with-chrome-headless</code> (or <code class="lang-none">with-chrome</code>) completely self-contains a webdriver instance, executes anything in the body with a <code class="lang-none">driver</code> <em>binding</em>, then unalives itself no matter what! That is clean, and pleasant.</p><h3 id="visits&#95;a&#95;page&#95;which,&#95;at&#95;some&#95;point,&#95;will&#95;contain&#95;a&#95;link&#95;to&#95;every&#95;desired&#95;article:">Visits a page which, at some point, will contain a link to every desired article:</h3><pre><code class="lang-clojure">&#40;e/go &quot;url ...&quot;&#41;
;; You then perform subsequent actions.
</code></pre><p>Easy!</p><p>(<code class="lang-none">e/go</code> returns nothing relevant)<a href='#fn-1' id='fnref1'><sup>1</sup></a></p><h3 id="loads&#95;the&#95;whole&#95;page:">Loads the whole page:</h3><p>I forsook this when parsing a simpler archive, this snippet is just a directly adapted proof of concept to demonstrate waiting.</p><pre><code class="lang-clojure">&#40;let &#91;heights &#40;atom &#91;1 0&#93;&#41;&#93;
  &#40;while &#40;not &#40;zero? &#40;apply - @heights&#41;&#41;&#41;
    &#40;e/scroll-bottom d&#41;
    &#40;e/wait 0.5&#41;
    &#40;e/wait-invisible d {:class :post-preview-silhouette}&#41;
    &#40;reset! heights &#91;&#40;e/js-execute d &quot;return document.body.scrollHeight&quot;&#41;
                     &#40;first @heights&#41;&#93;&#41;&#41;&#41;
</code></pre><p>Substack archives have infinite scroll. When you hit the bottom, it temporarily shows their version of a spinner with a particular classes. More posts have loaded, or the end has been reached.</p><p>This is the exact behavior recreated from the original script. How can it be improved? It’s messy, and depends on the temporary element loading by a given time, then compares the current and previous scroll heights.</p><h3 id="saves&#95;the&#95;desired&#95;information,&#95;like&#95;the&#95;main&#95;author,&#95;and&#95;list&#95;of&#95;all&#95;posts:">Saves the desired information, like the main author, and list of all posts:</h3><p>In theory… The entire archive of articles will now be loaded on the page.</p><pre><code class="lang-clojure">&#40;e/get-element-text-el
 driver
 &#40;e/query driver {:css &quot;#content header h3.author&quot;}&#41;&#41; ;; e.g
;; =&gt; &quot;Some person&quot;

&#40;mapv #&#40;get-element-inner-html-el driver %&#41;
	  &#40;e/query-all driver {:css &quot;#content .article-block&quot;}&#41;&#41;
;; =&gt; &#91;&quot;&lt;h4&gt;My big article&lt;/h4&gt;\n&lt;a href='https://...'&gt;&lt;img&gt;&lt;/a&gt;&quot;
;;     &quot;&lt;h4&gt;Another post..&lt;/h4&gt;\n&lt;a href='https://...'&gt;&lt;img&gt;&lt;/a&gt;&quot;&#93;
</code></pre><p>This snippet and the above would either be in a driver block, or relying on <code class="lang-none">&#40;def driver &#40;e/chrome ...&#41;&#41;</code>.</p><p>I was having a bit of a hurdle getting text/HTML results from different parts of a node all at once. But, the answer is straightforward!</p><p>Simplified <a href='http://blog.fogus.me/2012/08/23/minimum-viable-snippet/'>mvs</a>:</p><pre><code class="lang-clojure">&#40;defn get-articles
  &#91;d&#93;
  &#40;let &#91;els &#40;e/query-all d {:css &quot;.article&quot;}&#41;
        f &#40;fn &#91;el&#93;
            {:title &#40;e/get-element-text-el d
                      &#40;e/child d el {:css &quot;.article-title&quot;}&#41;&#41;
             :url &#40;e/get-element-attr-el d
                    &#40;e/child d el {:css &quot;.article-title a&quot;}&#41;&#41;}&#41;&#93;
    &#40;mapv f els&#41;&#41;&#41;

&#40;defn scrape
  &#91;archive-url&#93;
  &#40;e/with-chrome-headless driver
    &#40;e/go archive-url&#41;
    &#40;get-articles driver&#41;&#41;&#41;
;; =&gt; &#91;{:title &quot;My article&quot; :url &quot;https://...&quot;}
;;     {:title &quot;Another article&quot; :url &quot;https://..&quot;}&#93;
</code></pre><p>That is actually soooo sexy, simple, and reasonable. Once I got out of the py/<code class="lang-none">selenium</code> style of mutating collectors, wow! This could be further streamlined or greatly expanded, it’s just an example for clarity.</p><h3 id="and&#95;scrapes&#95;the&#95;content&#95;of&#95;a&#95;post.">And scrapes the content of a post.</h3><p>Hm, it started to get odd here. The script individuates all top-level elements within the content block, filters out substack subscribe nudges, and pastes the article back together. That’s easy to query all the elements, and retaining each element separately makes later parsing easier. But why can’t I find an <code class="lang-none">outer-html</code> function?</p><p>Not a hint why, but one solution, <a href='https://clojurians.slack.com/archives/C7KDM0EKW/p1526376258000298'>@borkdude mentions in 2018</a> when trying to accomplish an <code class="lang-none">outer-html</code> grab:</p><pre><code class="lang-clojure">&#40;e/get-element-attr-el driver element &quot;outerHTML&quot;&#41;
</code></pre><p>Yeah works fine… But I agree it’s kind of weird it doesn’t exist. What about defining my own to learn more?</p><p>Here’s the source for <code class="lang-none">e/get-element-inner-html-el</code>:</p><pre><code class="lang-clojure">&#40;defmethod get-element-inner-html-el
  :default
  &#91;driver el&#93;
  {:pre &#91;&#40;some? el&#41;&#93;}
  &#40;:value &#40;execute {:driver driver
                    :method :get
                    :path &#91;:session &#40;:session driver&#41;
                           :element el
                           :property :innerHTML&#93;}&#41;&#41;&#41;
</code></pre><p>Go deeper, <code class="lang-none">etaoin.api/execute</code> → <code class="lang-none">etaoin.impl.client/call</code> → it’s just using <code class="lang-none">clj.http/client</code> to make a browser request for the given property of a given element!</p><p>It should be trivial to copy the syntax<a href='#fn-2' id='fnref2'><sup>2</sup></a> and just change the property! And, it is:</p><pre><code class="lang-clojure">&#40;defn get-element-outer-html-el
  &#91;driver el&#93;
  &#40;:value &#40;e/execute {:driver driver
                      :method :get
                      :path &#91;:session &#40;:session driver&#41;
                             :element el
                             :property :outerHTML&#93;}&#41;&#41;&#41;
</code></pre><p>Then, it can be plugged into a query for an individual article page’s content:</p><pre><code class="lang-clojure">&#40;defn text-html
  &#91;&#93;
  &#40;e/with-chrome-headless
    driver
    &#40;e/go driver &quot;https://...&quot;&#41;
    &#40;mapv #&#40;get-element-outer-html-el driver %&#41;
          &#40;e/query-all driver {:css &quot;.field-name-body .field-item &gt; &#42;&quot;}&#41;&#41;&#41;&#41;
;; =&gt; &#91;&quot;&lt;p&gt;Thanks for reading this essay.&lt;/p&gt;&quot;
;;     &quot;&lt;p&gt;Next paragraph&lt;/p&gt;&quot;
;;     &quot;&lt;img src=\&quot;photo.jpg\&quot;&gt;&quot;&#93;
</code></pre><p>Wow, that’s really all I need. It’s surprising actually. I have achieved the goal of having a robot visit a website, read an article really quickly, and report back to me what it learned. Thanks robot!</p><hr/><p>The practice project that implements what I described is <a href='https://github.com/kees-/mag-stripe'>here, kees-/mag-stripe</a>.</p><ol class='footnotes'><li id='fn-1'>Browser-mutating commands (some? all?) return a minimal session ID map.<a href='#fnref1' target='_self'>&#8617;</a></li><li id='fn-2'>Why is the original code a mm? Because, for another webdriver type (<code class="lang-none">phantom</code>), the syntax is slightly different to get a property. I’m not using <code class="lang-none">phantom</code>, it’s fine, I just write a function.<a href='#fnref2' target='_self'>&#8617;</a></li></ol>]]></content>
  </entry>
</feed>
