<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://petermolnar.net/feed</id>
  <title> - petermolnar.net</title>
  <updated>2018-04-17T18:00:00+00:00</updated>
  <author>
    <name>Peter Molnar</name>
    <email>mail@petermolnar.net</email>
  </author>
  <link href="https://petermolnar.net/feed" rel="self"/>
  <generator uri="http://lkiesow.github.io/python-feedgen" version="0.6.1">python-feedgen</generator>
  <logo>https://petermolnar.net/favicon.png</logo>
  <entry>
    <id>https://petermolnar.net/how-to-make-a-print-css</id>
    <title>Guide on how to make your website printable with CSS</title>
    <updated>2018-04-17T18:00:00+00:00</updated>
    <content type="CDATA"><![CDATA[<h2 id="printing-its-2018">Printing?! It's 2018!</h2>
<p>&quot;Printing&quot; doesn't always mean putting it on paper. When people print a web article, sometimes it ends up as a PDF, because the HTML save it not usable. The reasons for this differ: JavaScript rendered content, unsaved scripts in the end result, the lack of MHTML support in browsers, etc. What's important is that providing a print-friendly format for your site makes it possible for people to save it in a usable way.</p>
<p>Printing might still be relevant, because that's the only method that gives you a physical object. I have long entries about journeys, visits of foreign places. At a certain point in time I was tempted to put together a photobook from the images there, but the truth is: it's a lot of work, especially if you've more or less done it already once by writing your entry.</p>
<p>There's also the completely valid case of archiving: hard copies have a life of decades if not centuries, when stored properly, unlike any electronic media we currently have as an option.</p>
<h2 id="that-little-extra-css">That little extra CSS</h2>
<p>Before jumping into the various hacks that helps printers it's important to mention, how to add printer-only instructions to your CSS. There are two ways, either using:</p>
<div class="sourceCode"><pre class="sourceCode css"><code class="sourceCode css"><span class="dv">@media</span> <span class="dv">print {</span>

<span class="dv">}</span></code></pre></div>
<p>inside an existing CSS, or by adding another CSS file specifically for <code>print</code> media into the HTML <code>&lt;head&gt;</code> section:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw">&lt;head&gt;</span>
    <span class="kw">&lt;link</span><span class="ot"> rel=</span><span class="st">&quot;stylesheet&quot;</span><span class="ot"> type=</span><span class="st">&quot;text/css&quot;</span><span class="ot"> href=</span><span class="st">&quot;print.css&quot;</span><span class="ot"> media=</span><span class="st">&quot;print&quot;</span><span class="kw">&gt;</span>
<span class="kw">&lt;/head&gt;</span></code></pre></div>
<h2 id="recommended-styling">Recommended styling</h2>
<h3 id="white-background-blackish-text">White background, black(ish) text</h3>
<p>Most printers operate with plain, white paper, so unless there's a very, very good reason for printing background color, just get rid of it.</p>
<p>It also applies to the font: a bit lighter from black, so saves tint.</p>
<div class="sourceCode"><pre class="sourceCode css"><code class="sourceCode css">* <span class="kw">{</span>
    <span class="kw">background-color:</span> <span class="dt">#fff</span> <span class="kw">!important;</span>
    <span class="kw">color:</span> <span class="dt">#222</span><span class="kw">;</span>
<span class="kw">}</span></code></pre></div>
<h3 id="use-printer-and-pdf-safe-fonts">Use printer and PDF safe fonts</h3>
<p>If you take a look at the history of printers vs fonts there used to be many problems around this topic - even so they might still require a font cartridge to be able to properly print fonts out of the basic options.<a href="#fn1" class="footnoteRef" id="fnref1"><sup>1</sup></a></p>
<p>To avoid rendering problems, aliasing issues, generally speaking: unreadable fonts, stick to one of the base 14 fonts:</p>
<ul>
<li>Courier, Courier-Bold, Courier-Oblique, Courier-BoldOblique</li>
<li>Helvetica, Helvetica-Bold, Helvetica-Oblique, Helvetica-BoldOblique</li>
<li>Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic</li>
<li>Symbol</li>
<li>ZapfDingbats</li>
</ul>
<p>which are, by definition, part of the PDF standard<a href="#fn2" class="footnoteRef" id="fnref2"><sup>2</sup></a>. So for example:</p>
<div class="sourceCode"><pre class="sourceCode css"><code class="sourceCode css">* <span class="kw">{</span>
    <span class="kw">font-size:</span> <span class="dt">11pt</span> <span class="kw">!important;</span>
    <span class="kw">font-family:</span> Helvetica, <span class="dt">sans-serif</span> <span class="kw">!important;</span>
<span class="kw">}</span></code></pre></div>
<p>If you do insist on special fonts, eg. you have icons in fonts, you might want to consider using SVG instead of fonts for icons - otherwise printing them properly will become a problem.</p>
<p>Besides the potential printing issues one more reason to go with a standard, base font is that if for any reason the text needs to go through character recognition for scanning it back - say it's an archival hard copy and the only one left after a data loss indicent - the simpler and wider known the font, the better your chances for getting the characters properly recognized.</p>
<h3 id="pages-and-page-breaks">Pages and page breaks</h3>
<p>It's very annoying to find a heading at the bottom of a printed page, or a paragraph broke into separate pages, although this latter depends on paragraph length. I generally recommend disallowing page breaks at these locations.</p>
<p>Apart from this it's a good idea to have a margin around the edges so you have an area where you can handle the page, not covering any of the text, or where it can be glued together as pages in a book.</p>
<div class="sourceCode"><pre class="sourceCode css"><code class="sourceCode css">@page <span class="kw">{</span>
    <span class="kw">margin:</span> <span class="dt">0.5in</span><span class="kw">;</span>
<span class="kw">}</span>

h1, h2, h3, h4, h5, h6 <span class="kw">{</span>
    <span class="kw">page-break-after:</span> <span class="dt">avoid</span> <span class="kw">!important;</span>
<span class="kw">}</span>

p, li, blockquote, figure, img <span class="kw">{</span>
    <span class="kw">page-break-inside:</span> <span class="dt">avoid</span> <span class="kw">!important;</span>
<span class="kw">}</span></code></pre></div>
<h3 id="images">Images</h3>
<p>Printing images is tricky: most of the images are sized for the web and those sizing are too small by resolution, too large by percentage of space taken for printing. The alt-text and the image headline, which is usually in <code>alt</code> and <code>title</code> are also something to consider printing, but unfortunately the <code>href</code> trick doesn't work with them: that is because you can't add <code>::before</code> or <code>::after</code> to self-closing tags, such as images.</p>
<p>Lately, instead of using simple <code>img</code> tags, I switched to using <code>figure</code>, along with <code>figcaption</code> - this way the headline became possible to print.</p>
<p>Apart from this I've limited the size of the images by view-width and view-height, so they never become too large and occupy complete pages.</p>
<div class="sourceCode"><pre class="sourceCode css"><code class="sourceCode css">figure <span class="kw">{</span>
    <span class="kw">margin:</span> <span class="dt">1rem</span> <span class="dt">0</span><span class="kw">;</span>
<span class="kw">}</span>

figcaption <span class="kw">{</span>
    <span class="kw">text-align:</span> <span class="dt">left</span><span class="kw">;</span>
    <span class="kw">margin:</span> <span class="dt">0</span> <span class="dt">auto</span><span class="kw">;</span>
    <span class="kw">padding:</span> <span class="dt">0.6rem</span><span class="kw">;</span>
    <span class="kw">font-size:</span> <span class="dt">0.9rem</span><span class="kw">;</span>
<span class="kw">}</span>

figure img <span class="kw">{</span>
    <span class="kw">display:</span> <span class="dt">block</span><span class="kw">;</span>
    <span class="kw">max-height:</span> <span class="dt">35vh</span><span class="kw">;</span>
    <span class="kw">max-width:</span> <span class="dt">90vw</span><span class="kw">;</span>
    <span class="kw">outline:</span> <span class="dt">none</span><span class="kw">;</span>
    <span class="kw">width:</span> <span class="dt">auto</span><span class="kw">;</span>
    <span class="kw">height:</span> <span class="dt">auto</span><span class="kw">;</span>
    <span class="kw">margin:</span> <span class="dt">0</span> <span class="dt">auto</span><span class="kw">;</span>
    <span class="kw">padding:</span> <span class="dt">0</span><span class="kw">;</span>
<span class="kw">}</span></code></pre></div>
<p>This is how <code>images</code> inside <code>figure</code> (should) look in print with the styling above:</p>
<figure class="photo">
<a href="https://petermolnar.net/files/css-print-example-photos_b.jpg" class=""> <img src="https://petermolnar.net/files/css-print-example-photos_z.jpg" title="" alt="This is how images can look like when some width/height limitations are applied in printing" class="adaptimg" width="584" height="640" /> </a>
<figcaption>
This is how images can look like when some width/height limitations are applied in printing
</figcaption>
</figure>
<h3 id="source-codes">Source codes</h3>
<p>If you have code blocks in your page it's useful to have them coloured, but still dark-on-light.</p>
<p>I'm using Pandoc's built-in syntax highlighting<a href="#fn3" class="footnoteRef" id="fnref3"><sup>3</sup></a> and the following styling for printing:</p>
<div class="sourceCode"><pre class="sourceCode css"><code class="sourceCode css">code, pre <span class="kw">{</span>
    <span class="kw">max-width:</span> <span class="dt">96%</span><span class="kw">;</span>
    <span class="kw">border:</span> <span class="dt">none</span><span class="kw">;</span>
    <span class="kw">color:</span> <span class="dt">#222</span><span class="kw">;</span>
    <span class="kw">word-break:</span> break-all<span class="kw">;</span>
    <span class="kw">word-wrap:</span> break-word<span class="kw">;</span>
    <span class="kw">white-space:</span> <span class="dt">pre-wrap</span><span class="kw">;</span>
    <span class="kw">overflow:</span>initial<span class="kw">;</span>
    <span class="kw">page-break-inside:</span> enabled<span class="kw">;</span>
    <span class="kw">font-family:</span> <span class="st">&quot;Courier&quot;</span>, <span class="st">&quot;Courier New&quot;</span>, <span class="dt">monospace</span> <span class="kw">!important;</span>
<span class="kw">}</span>

pre <span class="kw">{</span>
    <span class="kw">border:</span> <span class="dt">1pt</span> <span class="dt">dotted</span> <span class="dt">#666</span><span class="kw">;</span>
    <span class="kw">padding:</span> <span class="dt">0.6em</span><span class="kw">;</span>
<span class="kw">}</span>

<span class="co">/* code within pre - this is to avoid double borders */</span>
pre code <span class="kw">{</span>
    <span class="kw">border:</span> <span class="dt">none</span><span class="kw">;</span>
<span class="kw">}</span>

code<span class="fl">.sourceCode</span> span    <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.al</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.at</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.bn</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.bu</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.cf</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.ch</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.co</span> <span class="kw">{</span> <span class="kw">color:</span> darkgray<span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.dt</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.dv</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.er</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.ex</span> <span class="kw">{</span> <span class="kw">color:</span> darkorange<span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.fl</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.fu</span> <span class="kw">{</span> <span class="kw">color:</span> darkorange<span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.im</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.kw</span> <span class="kw">{</span> <span class="kw">color:</span> darkcyan<span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.op</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.ot</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.pp</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.sc</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.ss</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">black</span><span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.st</span> <span class="kw">{</span> <span class="kw">color:</span> magenta<span class="kw">;</span> <span class="kw">}</span>
code<span class="fl">.sourceCode</span> span<span class="fl">.va</span> <span class="kw">{</span> <span class="kw">color:</span> darkturquoise<span class="kw">;</span> <span class="kw">}</span></code></pre></div>
<p>It should result in something similar:</p>
<figure class="photo">
<a href="https://petermolnar.net/files/css-print-example-sourcecode_b.png" class=""> <img src="https://petermolnar.net/files/css-print-example-sourcecode_z.png" title="" alt="Color printing source code" class="adaptimg" width="584" height="640" /> </a>
<figcaption>
Color printing source code
</figcaption>
</figure>
<h3 id="printing-links-and-theirs-urls">Printing links and theirs URLs</h3>
<h4 id="the-basic-css-solution">The basic CSS solution</h4>
<p>Links are the single most important things on the internet; they are the internet. However, when they get printed, the end result usually looks something like this:</p>
<figure class="photo">
<a href="https://petermolnar.net/files/css-print-example-urls-before_z.png" class=""> <img src="https://petermolnar.net/files/css-print-example-urls-before_z.png" title="" alt="Before showing URLs - example showing Wikipedia entry "Mozilla software rebranded by Debian"" class="adaptimg" width="640" height="115" /> </a>
<figcaption>
Before showing URLs - example showing Wikipedia entry &quot;Mozilla software rebranded by Debian&quot;
</figcaption>
</figure>
<p>In order to avoid this problem, the URLs behind the links need to be shown as if they were part of the text. There is a rather simple way to do it:</p>
<div class="sourceCode"><pre class="sourceCode css"><code class="sourceCode css">a<span class="dv">::after</span> <span class="kw">{</span>
    <span class="kw">content:</span> <span class="st">&quot; (&quot;</span> <span class="dt">attr(</span>href<span class="dt">)</span> <span class="st">&quot;) &quot;</span><span class="kw">;</span>
    <span class="kw">font-size:</span> <span class="dt">90%</span><span class="kw">;</span>
<span class="kw">}</span></code></pre></div>
<p>but unfortunately it makes the text rather ugly and very hard to read:</p>
<figure class="photo">
<a href="https://petermolnar.net/files/css-print-example-urls-after_z.png" class=""> <img src="https://petermolnar.net/files/css-print-example-urls-after_z.png" title="" alt="After showing URLs" class="adaptimg" width="640" height="134" /> </a>
<figcaption>
After showing URLs
</figcaption>
</figure>
<h4 id="aaron-gustafsons-solution4">Aaron Gustafson's solution<a href="#fn4" class="footnoteRef" id="fnref4"><sup>4</sup></a></h4>
<p>There is a very nice, minimalistic Javascript solution<a href="#fn5" class="footnoteRef" id="fnref5"><sup>5</sup></a> that collects all links on the page and converts them into footnotes on the fly, when it detects a print request.</p>
<p>This solution is way nicer, so I certainly recommend using this as well (it's a supplement for the CSS solution above) even if it requres Javascript: (this is a copy-paste solution, just put it in your header)</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw">&lt;script</span><span class="ot"> type=</span><span class="st">&quot;text/javascript&quot;</span><span class="kw">&gt;</span>
    <span class="co">// &lt;![CDATA[</span>
    <span class="co">/*------------------------------------------------------------------------------</span>
<span class="co">    Function:       footnoteLinks()</span>
<span class="co">    Author:         Aaron Gustafson (aaron at easy-designs dot net)</span>
<span class="co">    Creation Date:  8 May 2005</span>
<span class="co">    Version:        1.3</span>
<span class="co">    Homepage:       http://www.easy-designs.net/code/footnoteLinks/</span>
<span class="co">    License:        Creative Commons Attribution-ShareAlike 2.0 License</span>
<span class="co">                    http://creativecommons.org/licenses/by-sa/2.0/</span>
<span class="co">    Note:           This version has reduced functionality as it is a demo of</span>
<span class="co">                    the script&#39;s development</span>
<span class="co">    ------------------------------------------------------------------------------*/</span>
    <span class="kw">function</span> <span class="at">footnoteLinks</span>(containerID<span class="op">,</span>targetID) <span class="op">{</span>
      <span class="cf">if</span> (<span class="op">!</span><span class="va">document</span>.<span class="at">getElementById</span> <span class="op">||</span>
          <span class="op">!</span><span class="va">document</span>.<span class="at">getElementsByTagName</span> <span class="op">||</span>
          <span class="op">!</span><span class="va">document</span>.<span class="at">createElement</span>) <span class="cf">return</span> <span class="kw">false</span><span class="op">;</span>
      <span class="cf">if</span> (<span class="op">!</span><span class="va">document</span>.<span class="at">getElementById</span>(containerID) <span class="op">||</span>
          <span class="op">!</span><span class="va">document</span>.<span class="at">getElementById</span>(targetID)) <span class="cf">return</span> <span class="kw">false</span><span class="op">;</span>
      <span class="kw">var</span> container <span class="op">=</span> <span class="va">document</span>.<span class="at">getElementById</span>(containerID)<span class="op">;</span>
      <span class="kw">var</span> target    <span class="op">=</span> <span class="va">document</span>.<span class="at">getElementById</span>(targetID)<span class="op">;</span>
      <span class="kw">var</span> h2        <span class="op">=</span> <span class="va">document</span>.<span class="at">createElement</span>(<span class="st">&#39;h2&#39;</span>)<span class="op">;</span>
      <span class="va">addClass</span>.<span class="at">apply</span>(h2<span class="op">,</span>[<span class="st">&#39;printOnly&#39;</span>])<span class="op">;</span>
      <span class="kw">var</span> h2_txt    <span class="op">=</span> <span class="va">document</span>.<span class="at">createTextNode</span>(<span class="st">&#39;Links&#39;</span>)<span class="op">;</span>
      <span class="va">h2</span>.<span class="at">appendChild</span>(h2_txt)<span class="op">;</span>
      <span class="kw">var</span> coll <span class="op">=</span> <span class="va">container</span>.<span class="at">getElementsByTagName</span>(<span class="st">&#39;*&#39;</span>)<span class="op">;</span>
      <span class="kw">var</span> ol   <span class="op">=</span> <span class="va">document</span>.<span class="at">createElement</span>(<span class="st">&#39;ol&#39;</span>)<span class="op">;</span>
      <span class="va">addClass</span>.<span class="at">apply</span>(ol<span class="op">,</span>[<span class="st">&#39;printOnly&#39;</span>])<span class="op">;</span>
      <span class="kw">var</span> myArr <span class="op">=</span> []<span class="op">;</span>
      <span class="kw">var</span> thisLink<span class="op">;</span>
      <span class="kw">var</span> num <span class="op">=</span> <span class="dv">1</span><span class="op">;</span>
      <span class="cf">for</span> (<span class="kw">var</span> i<span class="op">=</span><span class="dv">0</span><span class="op">;</span> i<span class="op">&lt;</span><span class="va">coll</span>.<span class="at">length</span><span class="op">;</span> i<span class="op">++</span>) <span class="op">{</span>
        <span class="kw">var</span> thisClass <span class="op">=</span> coll[i].<span class="at">className</span><span class="op">;</span>
        <span class="cf">if</span> ( coll[i].<span class="at">getAttribute</span>(<span class="st">&#39;href&#39;</span>) <span class="op">||</span>
             coll[i].<span class="at">getAttribute</span>(<span class="st">&#39;cite&#39;</span>) ) <span class="op">{</span>
          thisLink <span class="op">=</span> coll[i].<span class="at">getAttribute</span>(<span class="st">&#39;href&#39;</span>) <span class="op">?</span> coll[i].<span class="at">href</span> : coll[i].<span class="at">cite</span><span class="op">;</span>
          <span class="kw">var</span> note <span class="op">=</span> <span class="va">document</span>.<span class="at">createElement</span>(<span class="st">&#39;sup&#39;</span>)<span class="op">;</span>
          <span class="va">addClass</span>.<span class="at">apply</span>(note<span class="op">,</span>[<span class="st">&#39;printOnly&#39;</span>])<span class="op">;</span>
          <span class="kw">var</span> note_txt<span class="op">;</span>
          <span class="kw">var</span> j <span class="op">=</span> <span class="va">inArray</span>.<span class="at">apply</span>(myArr<span class="op">,</span>[thisLink])<span class="op">;</span>
          <span class="cf">if</span> ( j <span class="op">||</span> j<span class="op">===</span><span class="dv">0</span> ) <span class="op">{</span>
            note_txt <span class="op">=</span> <span class="va">document</span>.<span class="at">createTextNode</span>(j<span class="op">+</span><span class="dv">1</span>)<span class="op">;</span>
          <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span>
            <span class="kw">var</span> li     <span class="op">=</span> <span class="va">document</span>.<span class="at">createElement</span>(<span class="st">&#39;li&#39;</span>)<span class="op">;</span>
            <span class="kw">var</span> li_txt <span class="op">=</span> <span class="va">document</span>.<span class="at">createTextNode</span>(thisLink)<span class="op">;</span>
            <span class="va">li</span>.<span class="at">appendChild</span>(li_txt)<span class="op">;</span>
            <span class="va">ol</span>.<span class="at">appendChild</span>(li)<span class="op">;</span>
            <span class="va">myArr</span>.<span class="at">push</span>(thisLink)<span class="op">;</span>
            note_txt <span class="op">=</span> <span class="va">document</span>.<span class="at">createTextNode</span>(num)<span class="op">;</span>
            num<span class="op">++;</span>
          <span class="op">}</span>
          <span class="va">note</span>.<span class="at">appendChild</span>(note_txt)<span class="op">;</span>
          <span class="cf">if</span> (coll[i].<span class="va">tagName</span>.<span class="at">toLowerCase</span>() <span class="op">==</span> <span class="st">&#39;blockquote&#39;</span>) <span class="op">{</span>
            <span class="kw">var</span> lastChild <span class="op">=</span> <span class="va">lastChildContainingText</span>.<span class="at">apply</span>(coll[i])<span class="op">;</span>
            <span class="va">lastChild</span>.<span class="at">appendChild</span>(note)<span class="op">;</span>
          <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span>
            coll[i].<span class="va">parentNode</span>.<span class="at">insertBefore</span>(note<span class="op">,</span> coll[i].<span class="at">nextSibling</span>)<span class="op">;</span>
          <span class="op">}</span>
        <span class="op">}</span>
      <span class="op">}</span>
      <span class="va">target</span>.<span class="at">appendChild</span>(h2)<span class="op">;</span>
      <span class="va">target</span>.<span class="at">appendChild</span>(ol)<span class="op">;</span>
      <span class="va">addClass</span>.<span class="at">apply</span>(<span class="va">document</span>.<span class="at">getElementsByTagName</span>(<span class="st">&#39;html&#39;</span>)[<span class="dv">0</span>]<span class="op">,</span>[<span class="st">&#39;noted&#39;</span>])<span class="op">;</span>
      <span class="cf">return</span> <span class="kw">true</span><span class="op">;</span>
    <span class="op">}</span>
    <span class="va">window</span>.<span class="at">onload</span> <span class="op">=</span> <span class="kw">function</span>() <span class="op">{</span>
      <span class="at">footnoteLinks</span>(<span class="st">&#39;content&#39;</span><span class="op">,</span><span class="st">&#39;content&#39;</span>)<span class="op">;</span>
    <span class="op">}</span>
    <span class="co">// ]]&gt;</span>
  <span class="kw">&lt;/script&gt;</span>
  <span class="kw">&lt;script</span><span class="ot"> type=</span><span class="st">&quot;text/javascript&quot;</span><span class="kw">&gt;</span>
    <span class="co">// &lt;![CDATA[</span>
    <span class="co">/*------------------------------------------------------------------------------</span>
<span class="co">    Excerpts from the jsUtilities Library</span>
<span class="co">    Version:        2.1</span>
<span class="co">    Homepage:       http://www.easy-designs.net/code/jsUtilities/</span>
<span class="co">    License:        Creative Commons Attribution-ShareAlike 2.0 License</span>
<span class="co">                    http://creativecommons.org/licenses/by-sa/2.0/</span>
<span class="co">    Note:           If you change or improve on this script, please let us know.</span>
<span class="co">    ------------------------------------------------------------------------------*/</span>
    <span class="cf">if</span>(<span class="va">Array</span>.<span class="va">prototype</span>.<span class="at">push</span> <span class="op">==</span> <span class="kw">null</span>) <span class="op">{</span>
      <span class="va">Array</span>.<span class="va">prototype</span>.<span class="at">push</span> <span class="op">=</span> <span class="kw">function</span>(item) <span class="op">{</span>
        <span class="kw">this</span>[<span class="kw">this</span>.<span class="at">length</span>] <span class="op">=</span> item<span class="op">;</span>
        <span class="cf">return</span> <span class="kw">this</span>.<span class="at">length</span><span class="op">;</span>
      <span class="op">};</span>
    <span class="op">};</span>
    <span class="co">// ---------------------------------------------------------------------</span>
    <span class="co">//                  function.apply (if unsupported)</span>
    <span class="co">//           Courtesy of Aaron Boodman - http://youngpup.net</span>
    <span class="co">// ---------------------------------------------------------------------</span>
    <span class="cf">if</span> (<span class="op">!</span><span class="va">Function</span>.<span class="va">prototype</span>.<span class="at">apply</span>) <span class="op">{</span>
      <span class="va">Function</span>.<span class="va">prototype</span>.<span class="at">apply</span> <span class="op">=</span> <span class="kw">function</span>(oScope<span class="op">,</span> args) <span class="op">{</span>
        <span class="kw">var</span> sarg <span class="op">=</span> []<span class="op">;</span>
        <span class="kw">var</span> rtrn<span class="op">,</span> call<span class="op">;</span>
        <span class="cf">if</span> (<span class="op">!</span>oScope) oScope <span class="op">=</span> window<span class="op">;</span>
        <span class="cf">if</span> (<span class="op">!</span>args) args <span class="op">=</span> []<span class="op">;</span>
        <span class="cf">for</span> (<span class="kw">var</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> <span class="va">args</span>.<span class="at">length</span><span class="op">;</span> i<span class="op">++</span>) <span class="op">{</span>
          sarg[i] <span class="op">=</span> <span class="st">&quot;args[&quot;</span><span class="op">+</span>i<span class="op">+</span><span class="st">&quot;]&quot;</span><span class="op">;</span>
        <span class="op">};</span>
        call <span class="op">=</span> <span class="st">&quot;oScope.__applyTemp__(&quot;</span> <span class="op">+</span> <span class="va">sarg</span>.<span class="at">join</span>(<span class="st">&quot;,&quot;</span>) <span class="op">+</span> <span class="st">&quot;);&quot;</span><span class="op">;</span>
        <span class="va">oScope</span>.<span class="at">__applyTemp__</span> <span class="op">=</span> <span class="kw">this</span><span class="op">;</span>
        rtrn <span class="op">=</span> <span class="at">eval</span>(call)<span class="op">;</span>
        <span class="va">oScope</span>.<span class="at">__applyTemp__</span> <span class="op">=</span> <span class="kw">null</span><span class="op">;</span>
        <span class="cf">return</span> rtrn<span class="op">;</span>
      <span class="op">};</span>
    <span class="op">};</span>
    <span class="kw">function</span> <span class="at">inArray</span>(needle) <span class="op">{</span>
      <span class="cf">for</span> (<span class="kw">var</span> i<span class="op">=</span><span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> <span class="kw">this</span>.<span class="at">length</span><span class="op">;</span> i<span class="op">++</span>) <span class="op">{</span>
        <span class="cf">if</span> (<span class="kw">this</span>[i] <span class="op">===</span> needle) <span class="op">{</span>
          <span class="cf">return</span> i<span class="op">;</span>
        <span class="op">}</span>
      <span class="op">}</span>
      <span class="cf">return</span> <span class="kw">false</span><span class="op">;</span>
    <span class="op">}</span>
    <span class="kw">function</span> <span class="at">addClass</span>(theClass) <span class="op">{</span>
      <span class="cf">if</span> (<span class="kw">this</span>.<span class="at">className</span> <span class="op">!=</span> <span class="st">&#39;&#39;</span>) <span class="op">{</span>
        <span class="kw">this</span>.<span class="at">className</span> <span class="op">+=</span> <span class="st">&#39; &#39;</span> <span class="op">+</span> theClass<span class="op">;</span>
      <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span>
        <span class="kw">this</span>.<span class="at">className</span> <span class="op">=</span> theClass<span class="op">;</span>
      <span class="op">}</span>
    <span class="op">}</span>
    <span class="kw">function</span> <span class="at">lastChildContainingText</span>() <span class="op">{</span>
      <span class="kw">var</span> testChild <span class="op">=</span> <span class="kw">this</span>.<span class="at">lastChild</span><span class="op">;</span>
      <span class="kw">var</span> contentCntnr <span class="op">=</span> [<span class="st">&#39;p&#39;</span><span class="op">,</span><span class="st">&#39;li&#39;</span><span class="op">,</span><span class="st">&#39;dd&#39;</span>]<span class="op">;</span>
      <span class="cf">while</span> (<span class="va">testChild</span>.<span class="at">nodeType</span> <span class="op">!=</span> <span class="dv">1</span>) <span class="op">{</span>
        testChild <span class="op">=</span> <span class="va">testChild</span>.<span class="at">previousSibling</span><span class="op">;</span>
      <span class="op">}</span>
      <span class="kw">var</span> tag <span class="op">=</span> <span class="va">testChild</span>.<span class="va">tagName</span>.<span class="at">toLowerCase</span>()<span class="op">;</span>
      <span class="kw">var</span> tagInArr <span class="op">=</span> <span class="va">inArray</span>.<span class="at">apply</span>(contentCntnr<span class="op">,</span> [tag])<span class="op">;</span>
      <span class="cf">if</span> (<span class="op">!</span>tagInArr <span class="op">&amp;&amp;</span> tagInArr<span class="op">!==</span><span class="dv">0</span>) <span class="op">{</span>
        testChild <span class="op">=</span> <span class="va">lastChildContainingText</span>.<span class="at">apply</span>(testChild)<span class="op">;</span>
      <span class="op">}</span>
      <span class="cf">return</span> testChild<span class="op">;</span>
    <span class="op">}</span>
    <span class="co">// ]]&gt;</span>
  <span class="kw">&lt;/script&gt;</span>
  <span class="kw">&lt;style</span><span class="ot"> type=</span><span class="st">&quot;text/css&quot;</span><span class="ot"> media=</span><span class="st">&quot;screen&quot;</span><span class="kw">&gt;</span>
    <span class="fl">.printOnly</span> <span class="kw">{</span>
      <span class="kw">display:</span> <span class="dt">none</span><span class="kw">;</span>
    <span class="kw">}</span>
  <span class="kw">&lt;/style&gt;</span>
  <span class="kw">&lt;style</span><span class="ot"> type=</span><span class="st">&quot;text/css&quot;</span><span class="ot"> media=</span><span class="st">&quot;print&quot;</span><span class="kw">&gt;</span>
    a<span class="dv">:link:after</span>,
    a<span class="dv">:visited:after</span> <span class="kw">{</span>
      <span class="kw">content:</span> <span class="st">&quot; (&quot;</span> <span class="dt">attr(</span>href<span class="dt">)</span> <span class="st">&quot;) &quot;</span><span class="kw">;</span>
      <span class="kw">font-size:</span> <span class="dt">90%</span><span class="kw">;</span>
    <span class="kw">}</span>
    html<span class="fl">.noted</span> a<span class="dv">:link:after</span>,
    html<span class="fl">.noted</span> a<span class="dv">:visited:after</span> <span class="kw">{</span>
      <span class="kw">content:</span> <span class="st">&#39;&#39;</span><span class="kw">;</span>
    <span class="kw">}</span>
  <span class="kw">&lt;/style&gt;</span></code></pre></div>
<h4 id="alternative-approach-always-using-footnotes-for-urls">Alternative approach: always using footnotes for URLs</h4>
<p>I little while ago I made a decision to put all links into footnotes by default - no in-text-links which will bring you to another site. This is a design decision and doesn't apply to most of the already existing sites, but if you, just as me, think, there is value in it, consider it as an option. It also makes the two hacks above obsolete, however, it has it's own problems, such as reading the site entries via RSS.</p>
<h2 id="avoid">Avoid</h2>
<h3 id="opacity-and-transparency-it-can-get-blurry">opacity and transparency: it can get blurry</h3>
<p>A simple and sort of lazy solution to, instead of figuring out the proper color code, just apply opacity to a text to make it slightly different from the rest. Unfortunately some of these opacity settings can result in blurry or unusable text:</p>
<figure class="photo">
<a href="https://petermolnar.net/files/css-print-example-opacity_z.png" class=""> <img src="https://petermolnar.net/files/css-print-example-opacity_z.png" title="" alt="CSS opacity resulting in blurry text" class="adaptimg" width="640" height="202" /> </a>
<figcaption>
CSS opacity resulting in blurry text
</figcaption>
</figure>
<p>Therefore I suggest to avoid opacity and transparency on all elements for your printing styles.</p>
<p>Happy printing!</p>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p><a href="https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/print_c_fonts.mspx" class="uri">https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/print_c_fonts.mspx</a><a href="#fnref1">↩</a></p></li>
<li id="fn2"><p><a href="https://en.wikipedia.org/wiki/Portable_Document_Format#Standard_Type_1_Fonts_.28Standard_14_Fonts.29" class="uri">https://en.wikipedia.org/wiki/Portable_Document_Format#Standard_Type_1_Fonts_.28Standard_14_Fonts.29</a><a href="#fnref2">↩</a></p></li>
<li id="fn3"><p><a href="http://pandoc.org/MANUAL.html#syntax-highlighting" class="uri">http://pandoc.org/MANUAL.html#syntax-highlighting</a><a href="#fnref3">↩</a></p></li>
<li id="fn4"><p><a href="https://alistapart.com/article/improvingprint" class="uri">https://alistapart.com/article/improvingprint</a><a href="#fnref4">↩</a></p></li>
<li id="fn5"><p><a href="https://alistapart.com/d/improvingprint/files/final.html" class="uri">https://alistapart.com/d/improvingprint/files/final.html</a><a href="#fnref5">↩</a></p></li>
</ol>
</section>]]></content>
    <link href="https://petermolnar.net/how-to-make-a-print-css/" rel="alternate"/>
    <published>2018-04-17T18:00:00+00:00</published>
    <rights>2018 Peter Molnar CC BY 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/internet-emotional-core</id>
    <title>The internet that took over the Internet</title>
    <updated>2018-03-25T21:20:00+00:00</updated>
    <content type="CDATA"><![CDATA[<p>There is a video out there, titled &quot;The Fall of The Simpsons: How it Happened&quot;<a href="#fn1" class="footnoteRef" id="fnref1"><sup>1</sup></a>. It starts by introducing a mediocre show that airs every night, called &quot;The Simpsons&quot;, and compares it to a genius cartoon, that used to air in the early 90s, called &quot;The Simpsons&quot;. <em>Watch the video, because it's good, and I'm about to use it's conclusion</em>.</p>
<p>It reckons that the tremendous difference is due to shrinking layers in jokes, and, more importantly, in the characters after season 7. I believe something similar happened online, which made the Internet become the internet.</p>
<p>Many moons ago, while still living in London, the pedal of our flatmate's sewing machine broke down, and I started digging for replacement parts for her. I stumbled upon a detailed website about ancient capacitors<a href="#fn2" class="footnoteRef" id="fnref2"><sup>2</sup></a>. It resembled other, gorgeous sources of knowledge: one of my all time favourite is leofoo's site on historical Nikon equipment<a href="#fn3" class="footnoteRef" id="fnref3"><sup>3</sup></a>. All decades old sites, containing specialist level knowledge on topics only used to be found in books in dusty corners of forgotten libraries.</p>
<p>There's an interesting article about how chronological ordering destroyed the original way of curating content<a href="#fn4" class="footnoteRef" id="fnref4"><sup>4</sup></a> during the early online era, and I think the article got many things right. Try to imagine a slow web: slow connection, slow updates, slow everything. Take away social networks - no Twitter, no Facebook. Forget news aggregators: no more Hacker News or Reddit, not even Technorati. Grab your laptop and put in down on a desk, preferably in a corner - you're not allowed to move it. Use the HTML version of DuckDuckGo<a href="#fn5" class="footnoteRef" id="fnref5"><sup>5</sup></a> to search, and navigate with links from one site to another. That's how it was like; surfing on the <em>information highway</em>, and if you really want to experience it, UbuWeb<a href="#fn6" class="footnoteRef" id="fnref6"><sup>6</sup></a> will allow you to do so.</p>
<p>Most of the content was hand crafted, arranged to be readable, not searchable; it was human first, not machine first. Nearly everything online had a lot of effort put into it, even if the result was eye-blowing red text on blue background<a href="#fn7" class="footnoteRef" id="fnref7"><sup>7</sup></a>; somebody worked a lot on it. If you wanted it out there you learnt HTML, how to use FTP, how to link, how to format your page.</p>
<p>We used to have homepages. Homes on the Internet. <em>Not profiles, no; profile is something the authorities make about you in dossier.</em></p>
<p>6 years ago Anil Dash released a video, &quot;The web we lost&quot;<a href="#fn8" class="footnoteRef" id="fnref8"><sup>8</sup></a> and lamented the web 2.0 - <em>I despise this phrase; a horrible buzzword everyone used to label anything with; if you put 'cloud' and 'blockchain' together, you'll get the level of buzz that was 'web 2.0'</em> -, that fall short to social media, but make no mistake: the Internet, the carefully laboured web 1.0, had already went underground when tools made it simple for anyone to publish with just a few clicks.</p>
<p>The social web lost against social media, because it didn't (couldn't?) keep up with making things even simpler. Always on, always instant, always present. It served the purpose of a disposable web perfectly, where the most common goal is to seek fame, attention, to follow trends, to gain followers.</p>
<p>There are people who never gave up, and are still tirelessly building tools, protocols, ideas, to lead people out of social media. The IndieWeb<a href="#fn9" class="footnoteRef" id="fnref9"><sup>9</sup></a>'s goals are simple: own your data, have an online home, and connect with others through this. And so it's completely reasonable to hear:</p>
<blockquote>
<p>I want blogging to be as easy as tweeting.<a href="#fn10" class="footnoteRef" id="fnref10"><sup>10</sup></a></p>
</blockquote>
<p>But... what will this really achieve? This may sound rude and elitist, but the more I think about it the more I believe: the true way out of the swamp of social media is for things to require a little effort.</p>
<p>To make people think about what they produce, to make them connect to their online content. It's like IKEA<a href="#fn11" class="footnoteRef" id="fnref11"><sup>11</sup></a>: once you put time, and a minor amount of sweat - or swearing - into it, it'll feel more yours, than something comfortably delivered.</p>
<p>The Internet is still present, but it's shrinking. Content people really care about, customised looking homepages, carefully curated photo galleries are all diminishing. It would be fantastic to return to a world of personal websites, but that needs the love and work that used to be put into them, just like 20 years ago.</p>
<p>At this point in time, most people don't seem to relate to their online content. It's expendable. We need to make them care about it, and simpler tooling, on it's own, will not help with the lack of emotional connection.</p>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p><a href="https://www.youtube.com/watch?v=KqFNbCcyFkk" class="uri">https://www.youtube.com/watch?v=KqFNbCcyFkk</a><a href="#fnref1">↩</a></p></li>
<li id="fn2"><p><a href="http://www.vintage-radio.com/repair-restore-information/valve_capacitors.html" class="uri">http://www.vintage-radio.com/repair-restore-information/valve_capacitors.html</a><a href="#fnref2">↩</a></p></li>
<li id="fn3"><p><a href="http://www.mir.com.my/rb/photography/" class="uri">http://www.mir.com.my/rb/photography/</a><a href="#fnref3">↩</a></p></li>
<li id="fn4"><p><a href="https://stackingthebricks.com/how-blogs-broke-the-web/" class="uri">https://stackingthebricks.com/how-blogs-broke-the-web/</a><a href="#fnref4">↩</a></p></li>
<li id="fn5"><p><a href="https://duckduckgo.com/html/" class="uri">https://duckduckgo.com/html/</a><a href="#fnref5">↩</a></p></li>
<li id="fn6"><p><a href="http://www.slate.com/articles/technology/future_tense/2016/12/ubuweb_the_20_year_old_website_that_collects_the_forgotten_and_the_unfamiliar.html" class="uri">http://www.slate.com/articles/technology/future_tense/2016/12/ubuweb_the_20_year_old_website_that_collects_the_forgotten_and_the_unfamiliar.html</a><a href="#fnref6">↩</a></p></li>
<li id="fn7"><p><a href="http://code.divshot.com/geo-bootstrap/" class="uri">http://code.divshot.com/geo-bootstrap/</a><a href="#fnref7">↩</a></p></li>
<li id="fn8"><p><a href="http://anildash.com/2012/12/the-web-we-lost.html" class="uri">http://anildash.com/2012/12/the-web-we-lost.html</a><a href="#fnref8">↩</a></p></li>
<li id="fn9"><p><a href="https://indieweb.org" class="uri">https://indieweb.org</a><a href="#fnref9">↩</a></p></li>
<li id="fn10"><p><a href="http://www.manton.org/2018/03/indieweb-generation-4-and-hosted-domains.html" class="uri">http://www.manton.org/2018/03/indieweb-generation-4-and-hosted-domains.html</a><a href="#fnref10">↩</a></p></li>
<li id="fn11"><p><a href="https://en.wikipedia.org/wiki/IKEA_effect" class="uri">https://en.wikipedia.org/wiki/IKEA_effect</a><a href="#fnref11">↩</a></p></li>
</ol>
</section>]]></content>
    <link href="https://petermolnar.net/internet-emotional-core/" rel="alternate"/>
    <published>2018-03-25T21:20:00+00:00</published>
    <rights>2018 Peter Molnar CC BY 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/re-eli-20180318015703</id>
    <title>RE: https://eli.li/entry.php?id=20180318015703</title>
    <updated>2018-03-18T15:30:00+00:00</updated>
    <content type="CDATA"><![CDATA[<blockquote>
<p>Anyone with me? Am I totes off base?</p>
</blockquote>
<p>You are certainly not; the tools provided right now are indeed technical. However, you have to keep it in mind, that none of the specifications are finalised in any form - only webmentions are in W3C recommendation stage. <em>As it has been pointed out, my wording and knowledge was off: recommendation is the final, released stage, so I fixed it.</em></p>
<blockquote>
<p>The vast majority of users aren’t going to read the spec., nor care to ever do so. [...] This should be our (the IndieWeb’s) holy mission — empowering all sorts of folks to post content that they get to control.</p>
</blockquote>
<p>This is where we disagree. There are other, interesting movements, two of them with very similar goals: Repair Café and Restart Party. As the name suggests, it's about repairing things, instead of throwing them away, but both of them share the ideology of teaching people to fix their own things; to learn about their tools, to value, to own them.</p>
<p>Owning the content is the first step, but it shouldn't be the only step. I wholeheartedly disagree with WordPress' attitude, making people avoid technical responsibility<a href="#fn1" class="footnoteRef" id="fnref1"><sup>1</sup></a> - people should care, they should be at least be aware of what's happening when they press a publish button. They needn't have to be capable of doing it from scratch, but providing the tools only is not a goal I can align with.</p>
<p>If we keep up the attitude of making everything so simple that people don't have to understand, we'll very soon find ourselves in the chapter of The Foundation, where the priests keep running technology, without anyone understanding it.</p>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p><a href="https://www.rarst.net/wordpress/technical-responsibility/" class="uri">https://www.rarst.net/wordpress/technical-responsibility/</a><a href="#fnref1">↩</a></p></li>
</ol>
</section>]]></content>
    <link href="https://petermolnar.net/re-eli-20180318015703/" rel="alternate"/>
    <published>2018-03-18T15:30:00+00:00</published>
    <rights>2018 Peter Molnar CC BY-NC-ND 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/instant-messenger-hell</id>
    <title>We are living in instant messenger hell</title>
    <updated>2018-03-03T15:00:00+00:00</updated>
    <content type="CDATA"><![CDATA[<p><strong>Note: I have updated some parts of this entry. This is due to the fact that I wrote about XMP without spending enough time exploring what it's really capable of, for which I'm sorry. I made changes to my article according to these finds.</strong></p>
<h2 id="me-vs.-im">Me vs. IM</h2>
<p>Before the dawn of the always online era (pre 2007) the world of instant messengers was completely different. For me, it all started with various IRC<a href="#fn1" class="footnoteRef" id="fnref1"><sup>1</sup></a> rooms, using mIRC<a href="#fn2" class="footnoteRef" id="fnref2"><sup>2</sup></a>, later extended with ICQ<a href="#fn3" class="footnoteRef" id="fnref3"><sup>3</sup></a> in 1998.</p>
<p>I loved ICQ. I loved it's notifications sound - <em>I have it as notification sound on my smartphone and it usually results in very confused expressions from people who haven't heard the little 'ah-oooh' for a decade</em> -, it's capability of sending and receiving files, the way you could search for people based on location, interest tags, etc.</p>
<blockquote>
<p>The sixth protocol version appeared in ICQ 2000b and faced a complete rework. Encryption was significantly improved. Thanks to the new protocol, ICQ learned how to call phones, and send SMS and pager messages. Users also got the option of sending contact requests to other users.<a href="#fn4" class="footnoteRef" id="fnref4"><sup>4</sup></a></p>
</blockquote>
<p>Around this time, Windows included an instant messenger in their operating systems: MSN Messenger<a href="#fn5" class="footnoteRef" id="fnref5"><sup>5</sup></a>, later renamed to Windows Live Messenger. It was inferior, but because it was built in to Windows, it took all the ICQ users away. It's completely dead now.</p>
<p>The multiplication of messengers had one useful effect though: people who got fed up running multiple clients for the same purpose - to message people - came up with the idea if multi-protocol applications. I used Trillian<a href="#fn6" class="footnoteRef" id="fnref6"><sup>6</sup></a> for many years, followed by Pidgin<a href="#fn7" class="footnoteRef" id="fnref7"><sup>7</sup></a> once I switched to linux.</p>
<p>With the help of these multi-protocol miracles it wasn't an issues when newcomers like Facebook or Google released their messaging functionality: both were built in top of XMPP<a href="#fn8" class="footnoteRef" id="fnref8"><sup>8</sup></a>, an open standard for instant messaging, and they were both supported out of the box in those programs.</p>
<p>Around this time came Skype and it solved all the video call problems with ease. It was fast, p2p, encrypted, ran on every platform, supported more or less everything people needed, including multiple instances for multiple accounts. Skype was on a good way to eliminate everything else. Unfortunately none of the multi-protocol messengers ever had a native support to it: it only worked if a local instance of Skype was running.</p>
<p>A few years later iPhone appeared and it ate the consumer world; not long before that, BlackBerry did the same to the business. Smartphones came with their own, new issues: synchronization, and resource (battery and bandwidth) limitations. <em>ICQ existed for Symbian S60, Windows CE, and a bunch of other, ancient platforms, but by the time iPhones and BlackBerries roamed the mobile land, it was in a neglected state in AOL and missed a marvellous opportunity.</em></p>
<p>Both of those problems were known and addressed in the XMPP specification. The protocol was low on resources by design, it supported device priority, and XEP-0280: Message Carbons<a href="#fn9" class="footnoteRef" id="fnref9"><sup>9</sup></a> took care of delivering messages to multiple clients. There was a catch though: none of the well known XMPP providers supported any of these additions, so you ended up using either your mobile device or your computer exclusively at the same time. Most of the big system - AOL, Yahoo!, MSN, Skype, etc - didn't even have a client for iOS, let alone for Android that time.</p>
<p>This lead to a new type of messenger generation: mobile only apps. WhatsApp<a href="#fn10" class="footnoteRef" id="fnref10"><sup>10</sup></a>, BlackBerry Messenger<a href="#fn11" class="footnoteRef" id="fnref11"><sup>11</sup></a>, Viber, etc - none of them offered any real way to be used from the desktop, and they all required - they still do - a working mobile phone number even to register.</p>
<p>For reasons I'm yet to comprehend, both Google and Facebook abandoned XMPP instead of <del>extending</del> fully implementing it. Google went completely proprietary and replaced gtalk<a href="#fn12" class="footnoteRef" id="fnref12"><sup>12</sup></a> with Hangouts<a href="#fn13" class="footnoteRef" id="fnref13"><sup>13</sup></a>; Facebook started using MQTT<a href="#fn14" class="footnoteRef" id="fnref14"><sup>14</sup></a> for their messenger applications. Both of them were simple enough to be reverse engineered and added to libpurple, but they both tried to reinvent something that already existed.</p>
<p>For Skype, this was a turning point: it was bought by Microsoft, and they slowly moved it from p2p to a completely centralised webapp. The official reasoning included something about power hungry p2p connections... Soon, Skype lost all of it's appeal from it's previous iterations: video and voice was lagging, it was consuming silly amount of resources, it was impossible to stop it on Android, etc. Today, it resembles nothing from the original, incredible, p2p, secure, decentralised, resource-aware application it used to be.</p>
<p>I had to install WhatsApp yesterday - I resisted it as long as I could. It completely mangled competition in the UK and the Netherlands: nobody is willing to use anything else, not even regular text (SMS) or email. It did all this despite it's lack of multi-device support, and the fact that it's now owned by one of the nastiest, people-ignorant businesses around the globe<a href="#fn15" class="footnoteRef" id="fnref15"><sup>15</sup></a>.</p>
<p>So, all together, in February 2018, for work and personal communication, I need to be able to connect to:</p>
<ul>
<li>IRC</li>
<li>Skype</li>
<li>Facebook</li>
<li>Telegram</li>
<li>XMPP</li>
<li>Workplace by Facebook<a href="#fn16" class="footnoteRef" id="fnref16"><sup>16</sup></a></li>
<li>WhatsApp</li>
<li>ICQ*</li>
<li>Google Hangouts*</li>
<li>WeChat**</li>
</ul>
<p>* <em>I still have some contacts on ICQ, though it's a wasteland, and I can't even remember the last time I actually talked to anyone on it. This sort of applies to Hangouts: those who used to use it are now mostly on Facebook</em>.</p>
<p>** <em>WeChat is, so far, only a thing if you have Chinese contacts or if you live in/visit China. It's dominating China so far that other networks, like QQ, can be more or less ignored, but WeChat is essential.</em></p>
<p>If I install all those things on my phone, I'll run out of power in a matter of hours and the Nomu has an internal 5000mAh brick. They will consume any RAM I throw at them, and I don't even want to think about the privacy implications: out of curiosity I checked the ICQ app, but the policy pushed into my face on the first launch is rather scary. As for Facebook: I refuse to run Facebook in any form on my phone apart from 'mbasic', the no javascript web interface.</p>
<p>Typing on a touchscreen inefficient, and I'm very far from being a keyboard nerd; my logs will be application specific and probably not in any readable/parsable format.</p>
<p>On top of all this, a few days ago Google announced Google Hangouts Chat<a href="#fn17" class="footnoteRef" id="fnref17"><sup>17</sup></a>. Right now, Google has the following applications to cover text, voice, and video chat:</p>
<ul>
<li>Hangouts</li>
<li>Allo</li>
<li>Duo</li>
<li>Hangouts Meet</li>
<li>Hangouts Chat</li>
</ul>
<p>That's 5 applications. 5. Only from Google.</p>
<h2 id="words-for-the-future">Words for the future</h2>
<p>I really, really want one, single thing, which allows me:</p>
<ul>
<li>native voice and video</li>
<li>private and group messaging</li>
<li>multiple concurrent login from varios platforms</li>
<li>libpurple plugin option</li>
<li>all devices get all messages</li>
</ul>
<p>I sort of liked is Telegram<a href="#fn18" class="footnoteRef" id="fnref18"><sup>18</sup></a>: cross device support, surprisingly fast and low on resources, but it gets attacked because they dared to roll their own crypto, and, in the end, it's still a centralised service, ending up as just another account to connect to, and just another app to run. Since I wrote this entry, a few has tried to point out, that Telegram is not better, than WhatsApp or Signal, but I have to disagree. Yes, WhatsApp is encrypted by default - this also means I need to run my phone as a gateway all the time. No phone = no desktop user. The desktop &quot;app&quot; is a power and resource eater Electron app.</p>
<p>Others asked about Signal. It's doing encryption the paranoid, aggressive way, but the same time, it depends on Google Services Framework on Android, Twilio, AWS, requires a smartphone app, eliminates 3rd party client options, and will only run the &quot;desktop&quot; Electron app if you pair it with a phone app - in which case it's very similar to WhatsApp. Like it or not, it's also a silo, with centralised services, even though you could, in theory, be able to install a complicated server of your own, that relies on the services listed above. It might be better, then WhatsApp, definitely not better from a usability point of view, than Telegram. Privacy wise... unless I can run my own server, without those kind of dependencies, no, thanks - it's just another silo.</p>
<p>I also believe OTR-like encryption is overrated, or at least not as important as many presses. Most of the messages will tell you less, than their metadata, so what's the point? Most of the encryption protocols are exclusive per connected client, meaning you can't have multiple devices with the same account exchanging the same messages - hence the need for the phone apps as gateways. XMPP with OMEMO<a href="#fn19" class="footnoteRef" id="fnref19"><sup>19</sup></a> is tacking this - if that's on by default, that could work. <em>Note: TLS, infrastructure level encryption is a must, that is without question.</em></p>
<p>While Matrix<a href="#fn20" class="footnoteRef" id="fnref20"><sup>20</sup></a> looks promising, it's an everything-on-HTTP solution, which I still find odd and sad. HTTP is not a simple protocol - yes, it's omnipresent, but that doesn't make it the best for a particular purpose. There's another problem with it: no big player bought in which could bring the critical mass of users, and without that, it's practically impossible to get people to use it.</p>
<p>Video and voices calls are, in general, in a horrible shape: nearly everything is doing WebRTC, which, while usually works, is a terrible performer, insanely heavy on CPU, and, most of the time, always tries to go for the highest quality, consuming bandwidth like there is no tomorrow.</p>
<p>All this leaves me with <strong>XMPP</strong> and <strong>SIP</strong>.</p>
<p>XMPP is and could be able to cover everything, and, on top of it, it's federated, like email: anyone can run their own instance. I'm still a fan of email (<em>yes, you read that right</em>), and a signficant part of it is due to the options you can choose from: providers, clients, even being your own email service.</p>
<p>Unlike with most solutions and silos, the encryption problem (<em>namely that if encryption is on, only one of the devices can get the messages, our you need to use a router device, like WhatsApp does</em>) is covered and done with the XMPP extension OMEMO<a href="#fn21" class="footnoteRef" id="fnref21"><sup>21</sup></a>. It's a multi-client encryption protocoll, that allows simultaneous devices to connect and encrypt at once.</p>
<p>In case of XMPP, voice and video could be handled by a P2P protocol, Jingle<a href="#fn22" class="footnoteRef" id="fnref22"><sup>22</sup></a>, but, unfortunately, it's rarely supported. On Android, I found Astrachat<a href="#fn23" class="footnoteRef" id="fnref23"><sup>23</sup></a> which can do it, but it lacks many features when it comes to text based communications, unlike Conversations<a href="#fn24" class="footnoteRef" id="fnref24"><sup>24</sup></a>. On desktop, I'm having serious problems getting Pidgin use video, so not everything is working yet.</p>
<p>This is where SIP comes in: an old, battle tested, proven VOIP protocol, which, so far, worked for me without any glitch in 2018. A few years ago many mobile providers were blocking SIP (among other VOIP protocols), but it's getting much better. Unfortunately I have not started running my own VOIP exchange yet, and ended up using Linphone<a href="#fn25" class="footnoteRef" id="fnref25"><sup>25</sup></a> as software and provider - for now. The unfortunate part of SIP is that Pidgin doesn't support it in any form.</p>
<p><strong>There is one, very significant problem left: conformist people. I understand WhatsApp is simple and convenient, but it's a Facebook owned, phone only system.</strong></p>
<p><strong>I'd welcome thoughts and recommendations on how to make your friends use something that's not owned by Facebook.</strong></p>
<p>Until then, I'll keep using Pidgin, with a swarm of plugins that need constant updating.</p>
<h2 id="adding-networks-to-pidgin-technical-details">Adding networks to Pidgin (technical details)</h2>
<p>Pidgin, which I mentioned before, is a multi protocol client. Out of the box, it's in a pretty bad shape: AIM, MSN, and Google Talk are dead as doornail, most of the systems it supports are arcane (eg. Zephyr) or sort of forgotten (ICQ). The version 3 of pidgin, and it's library, libpurple, has been in the making for a decade and it's still far ahead; the current 2.x line is barely supported.</p>
<p>There is hope however: people keep adding support for new systems, even to ones without proper or documented API.</p>
<p><em>For those who want to stick to strictly text interfaces, Bitlbee has a way to be compiled with libpurple support, but it's a bit weird to use when you have the same contact or same names present on multiple networks.</em></p>
<p>The guides below are made for Debian and it's derivatives, like Ubuntu and Mint. In order to build any of the plugins below, some common build tools are needed, apart from the per plugin specific ones:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> apt install libprotobuf-dev protobuf-compiler build-essential
<span class="fu">sudo</span> apt-get build-dep pidgin</code></pre></div>
<h3 id="how-to-conect-to-skype-with-pidgin-or-libpurple">How to conect to Skype with Pidgin (or libpurple)</h3>
<p>The current iteration of the Skype plugin uses the web interface to connect to the system. It doesn't offer voice and video calls, but it supports individual and group chats alike.</p>
<p>If you have 2FA on, you'll need to use your app password as password and tick the <code>Use alternative login method</code> on the <code>Advanced</code> tab when adding the account.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">git</span> clone https://github.com/EionRobb/Skype4pidgin
<span class="bu">cd</span> Skype4pidgin/Skypeweb
<span class="fu">cmake</span> .
<span class="fu">make</span>
<span class="fu">sudo</span> make install</code></pre></div>
<h3 id="how-to-connect-to-google-hangouts-with-pidgin-or-libpurple">How to connect to Google Hangouts with Pidgin (or libpurple)</h3>
<p>I've taken the instructions from the author's bitbucket site<a href="#fn26" class="footnoteRef" id="fnref26"><sup>26</sup></a>:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> apt install -y libpurple-dev libjson-glib-dev libglib2.0-dev libprotobuf-c-dev protobuf-c-compiler mercurial make
<span class="ex">hg</span> clone https://bitbucket.org/EionRobb/purple-hangouts/
<span class="bu">cd</span> purple-hangouts
<span class="fu">make</span>
<span class="fu">sudo</span> make install</code></pre></div>
<h3 id="how-to-connec-to-facebook-andor-workplace-by-facebook-with-pidgin-or-libpurple">How to connec to Facebook and/or Workplace by Facebook with Pidgin (or libpurple)</h3>
<p>The Workplace support is not yet merged into the main code: it's in the <code>wip-work-chat</code> branch. More information in the support ticket<a href="#fn27" class="footnoteRef" id="fnref27"><sup>27</sup></a>.</p>
<p>Workplace and it's 'buddy' list is sort of a mystery at this point in time, so don't expect everything to run completely smooth, but it's much better, than nothing.</p>
<p>In order to log in to a Workplace account, tick <code>Login as Workplace account</code> on the <code>Advanced</code> tab.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">git</span> clone https://github.com/dequis/purple-facebook
<span class="bu">cd</span> purple-facebook
<span class="fu">git</span> checkout wip-work-chat
<span class="ex">./autogen.sh</span>
<span class="ex">./configure</span>
<span class="fu">make</span>
<span class="fu">sudo</span> make install</code></pre></div>
<h3 id="how-to-conect-to-telegram-with-pidgin-or-libpurple">How to conect to Telegram with Pidgin (or libpurple)</h3>
<p>The Telegram plugin works nicely, including inline images and and to end encrypted messages. Voice supports seems to be lacking unfortunately.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> apt install libgcrypt20-dev libwebp-dev
<span class="fu">git</span> clone https://github.com/majn/telegram-purple
<span class="bu">cd</span> telegram-purple
<span class="fu">git</span> submodule update --init --recursive
<span class="ex">./configure</span>
<span class="fu">make</span>
<span class="fu">sudo</span> make install</code></pre></div>
<h3 id="how-to-connect-to-whatsapp-with-pidgin-or-libpurple">How to connect to WhatsApp with Pidgin (or libpurple)</h3>
<p>Did I mention I hate this network? <strong>First of all a note: WhatsApp doesn't allow 3rd party applications at all. They might ban the phone number you use for life.</strong> This ban may be extended to Facebook with the same phone number but this has never been officially confirmed.</p>
<p>Apart from that it needs a lot of hacking around: the plugin is not enough, because WhatsApp doesn't tell you your password. In order to get your password, you need to fake a 'registration' from the computer.</p>
<p>Even if you do this, only one device will work: the other instances will get logged out, so there is no way to use WhatsApp from your phone and from your laptop. It's 2007 again, except it's mobile only instead of desktop only.</p>
<p><strong>Please stop using WhatsApp and use something with a tad more openness in it; XMPP, Telegram, SIP, ICQ... basically anything.</strong></p>
<p>If you're stuck with needing to communicate with stubborn and lazy people, like I am, continue reading, and install the plugin for pidgin:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> apt install libprotobuf-dev protobuf-compiler
<span class="fu">git</span> clone https://github.com/jakibaki/whatsapp-purple/
<span class="bu">cd</span> whatsapp-purple
<span class="fu">make</span>
<span class="fu">sudo</span> make install</code></pre></div>
<p>However, this is not enough: the next step is <code>yowsup</code>, a command line python utility that allows you to 'register' to WhatsApp and reveals that so well hidden password.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> pip3 install yowsup</code></pre></div>
<p>Once done, you need to first request an SMS, meaning you'll need a number that's able to receive SMS. Replace the <code>COUNTRYCODE</code> and <code>PHONENUMBER</code> string with your country code and phone number without prefixes, so for United Kingdom, that would be:</p>
<ul>
<li>country code: 44</li>
<li>phone number: 441234567890</li>
</ul>
<p>No 00, or + before the full international phone number.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">yowsup-cli</span> registration --requestcode sms -p PHONENUMBER --cc COUNTRYCODE --env android

    <span class="ex">yowsup-cli</span>  v2.0.15
    <span class="ex">yowsup</span>      v2.5.7

    <span class="ex">Copyright</span> (c) <span class="ex">2012-2016</span> Tarek Galal
    <span class="ex">http</span>://www.openwhatsapp.org

    <span class="ex">This</span> software is provided free of charge. Copying and redistribution is
    <span class="ex">encouraged.</span>

    <span class="ex">If</span> you appreciate this software and you would like to support future
    <span class="ex">development</span> please consider donating:
    <span class="ex">http</span>://openwhatsapp.org/yowsup/donate


    <span class="ex">INFO</span>:yowsup.common.http.warequest:b<span class="st">&#39;{&quot;login&quot;:&quot;PHONENUMBER&quot;,&quot;status&quot;:&quot;sent&quot;,&quot;length&quot;:6,&quot;method&quot;:&quot;sms&quot;,&quot;retry_after&quot;:78,&quot;sms_wait&quot;:78,&quot;voice_wait&quot;:65}\n&#39;</span>
    <span class="ex">status</span>: b<span class="st">&#39;sent&#39;</span>
    <span class="ex">length</span>: 6
    <span class="ex">method</span>: b<span class="st">&#39;sms&#39;</span>
    <span class="ex">retry_after</span>: 78
    <span class="ex">login</span>: b<span class="st">&#39;PHONENUMBER&#39;</span></code></pre></div>
<p>Once you got the SMS, use the secret code:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">yowsup-cli</span> registration --register SECRET-CODE -p PHONENUMBER --cc COUNTRYCODE --env android

    <span class="ex">yowsup-cli</span>  v2.0.15
    <span class="ex">yowsup</span>      v2.5.7

    <span class="ex">Copyright</span> (c) <span class="ex">2012-2016</span> Tarek Galal
    <span class="ex">http</span>://www.openwhatsapp.org

    <span class="ex">This</span> software is provided free of charge. Copying and redistribution is
    <span class="ex">encouraged.</span>

    <span class="ex">If</span> you appreciate this software and you would like to support future
    <span class="ex">development</span> please consider donating:
    <span class="ex">http</span>://openwhatsapp.org/yowsup/donate

    <span class="ex">INFO</span>:yowsup.common.http.warequest:b<span class="st">&#39;{&quot;status&quot;:&quot;ok&quot;,&quot;login&quot;:&quot;PHONENUMBER&quot;,&quot;type&quot;:&quot;existing&quot;,&quot;edge_routing_info&quot;:&quot;CAA=&quot;,&quot;chat_dns_domain&quot;:&quot;sl&quot;,&quot;pw&quot;:&quot;[YOUR WHATSAPP PASSWORD YOU NEED TO COPY]=&quot;,&quot;expiration&quot;:4444444444.0,&quot;kind&quot;:&quot;free&quot;,&quot;price&quot;:&quot;$0.99&quot;,&quot;cost&quot;:&quot;0.99&quot;,&quot;currency&quot;:&quot;USD&quot;,&quot;price_expiration&quot;:1520591114}\n&#39;</span>
    <span class="ex">status</span>: b<span class="st">&#39;ok&#39;</span>
    <span class="ex">login</span>: b<span class="st">&#39;PHONENUMBER&#39;</span>
    <span class="ex">pw</span>: b<span class="st">&#39;YOUR WHATSAPP PASSWORD YOU NEED TO COPY&#39;</span>
    <span class="ex">type</span>: b<span class="st">&#39;existing&#39;</span>
    <span class="ex">expiration</span>: 4444444444.0
    <span class="ex">kind</span>: b<span class="st">&#39;free&#39;</span>
    <span class="ex">price</span>: b<span class="st">&#39;$0.99&#39;</span>
    <span class="ex">cost</span>: b<span class="st">&#39;0.99&#39;</span>
    <span class="ex">currency</span>: b<span class="st">&#39;USD&#39;</span>
    <span class="ex">price_expiration</span>: 1520591114</code></pre></div>
<p>That <code>YOUR WHATSAPP PASSWORD YOU NEED TO COPY</code> is the password you need to put in the password field of the account; the username is your <code>PHONENUMBER</code>.</p>
<h3 id="how-to-connect-to-wechat-with-pidgin-or-libpurple">How to connect to WeChat with Pidgin (or libpurple)</h3>
<p>If there is something worse, than WhatsApp, it's WeChat: app only and rather agressive when it comes to accessing private data on the phone. If you want to use it, but avoid actually serving data to it, I recommend getting the Xposed Framework<a href="#fn28" class="footnoteRef" id="fnref28"><sup>28</sup></a> with XPrivacyLua<a href="#fn29" class="footnoteRef" id="fnref29"><sup>29</sup></a> on your phone before WeChat and restricting WeChat with it as much as possible.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> apt install cargo clang
<span class="fu">git</span> clone https://github.com/sbwtw/pidgin-wechat
<span class="bu">cd</span> pidgin-wechat
<span class="ex">cargo</span> build
<span class="fu">sudo</span> cp target/debug/libwechat.so /usr/lib/purple-2/</code></pre></div>
<p>Pidgin will only ask for a <code>username</code> - fill that in with you WeChat username and connect. Pidgin will soon pop up a window with a QR code - scan it with the WeChat app and follow the process on screen.</p>
<h3 id="other-networks">Other networks</h3>
<p>Pidgin has a list of third party plugins<a href="#fn30" class="footnoteRef" id="fnref30"><sup>30</sup></a>, but it's outdated. I've been searching for forks and networks missing from the list on Github.</p>
<h2 id="extra-plugins-for-pidgin">Extra Plugins for Pidgin</h2>
<h3 id="purple-plugin-pack">Purple Plugin Pack</h3>
<p>There are a few useful plugins for Pidgin that can make life simpler; the Purple Plugin Pack<a href="#fn31" class="footnoteRef" id="fnref31"><sup>31</sup></a> contains most of the ones in my list:</p>
<ul>
<li>Highlight</li>
<li>IRC helper</li>
<li>Message Splitter</li>
<li>XMPP Priority</li>
<li>Join/Part Hiding</li>
<li>Markerline</li>
<li>Message Timestamp Formats</li>
<li>Nick Change Hiding</li>
<li>Save Conversation Order</li>
<li>Voice/Viceo Settings</li>
<li>XMPP Service Discovery</li>
<li>Mystatusbox (Show Statusboxes)</li>
</ul>
<h3 id="xmpp-message-carbons">XMPP Message Carbons</h3>
<p>XEP-0280 Message Carbons<a href="#fn32" class="footnoteRef" id="fnref32"><sup>32</sup></a> is an extension that allows multiple devices to receive all messages.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> apt install libpurple-dev libglib2.0-dev libxml2-dev
<span class="fu">git</span> clone https://github.com/gkdr/carbons
<span class="bu">cd</span> carbons
<span class="fu">make</span>
<span class="fu">sudo</span> make install</code></pre></div>
<p>Once installed, open a chat or conversation that happens on the relevant server and type:</p>
<pre><code>/carbons on</code></pre>
<p>This will not be delivered as message but executed on the server as command. Unfortunately not all of the XMPP servers support this.</p>
<h3 id="omemo">OMEMO</h3>
<p>OMEMO<a href="#fn33" class="footnoteRef" id="fnref33"><sup>33</sup></a> is a multi-legged encryption protocol that allows encrypted messages across multiple devices. It's built-in into Conversations<a href="#fn34" class="footnoteRef" id="fnref34"><sup>34</sup></a>, one of the best XMPP clients for Android - Pidgin doesn't have it by default.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> apt install git cmake libpurple-dev libmxml-dev libxml2-dev libsqlite3-dev libgcrypt20-dev
<span class="fu">git</span> clone https://github.com/gkdr/lurch/
<span class="bu">cd</span> lurch
<span class="fu">git</span> submodule update --init --recursive
<span class="fu">make</span>
<span class="fu">sudo</span> make install</code></pre></div>
<h3 id="message-delivery-receipts30">Message Delivery Receipts<a href="#fn35" class="footnoteRef" id="fnref35"><sup>35</sup></a></h3>
<p>Yet another missing by default XMPP extension, which is quite useful.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">git</span> clone https://git.assembla.com/pidgin-xmpp-receipts.git 
<span class="bu">cd</span> pidgin-xmpp-receipts/
<span class="fu">make</span>
<span class="fu">sudo</span> cp xmpp-receipts.so /usr/lib/purple-2/</code></pre></div>
<h2 id="porting-old-logs-to-pidgin">Porting old logs to Pidgin</h2>
<p>I wrote a Python script which can port some old logs into Pidgin. It can deal with unmodifies logs from:</p>
<ul>
<li>Trillian (v3.x)</li>
<li>MSN Plus! HTML logs</li>
<li>Skype (v2.x)</li>
</ul>
<p>As for ZNC and Facebook, a lot of handywork is needed - see the comments in the script.</p>
<p>Requirements:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">pip3</span> install arrow bs4</code></pre></div>
<p>And the script:</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="im">import</span> os
<span class="im">import</span> sqlite3
<span class="im">import</span> logging
<span class="im">import</span> re
<span class="im">import</span> glob
<span class="im">import</span> sys
<span class="im">import</span> hashlib
<span class="im">import</span> arrow
<span class="im">import</span> argparse
<span class="im">from</span> bs4 <span class="im">import</span> BeautifulSoup
<span class="im">import</span> csv


<span class="kw">def</span> logfilename(dt, nulltime<span class="op">=</span><span class="va">False</span>):
    <span class="cf">if</span> nulltime:
        t <span class="op">=</span> <span class="st">&#39;000000&#39;</span>
    <span class="cf">else</span>:
        t <span class="op">=</span> dt.<span class="bu">format</span>(<span class="st">&#39;HHmmss&#39;</span>)

    <span class="cf">return</span> <span class="st">&quot;</span><span class="sc">%s</span><span class="st">.</span><span class="sc">%s%s%s</span><span class="st">.txt&quot;</span> <span class="op">%</span> (
        dt.<span class="bu">format</span>(<span class="st">&quot;YYYY-MM-DD&quot;</span>),
        t,
        dt.datetime.strftime(<span class="st">&quot;%z&quot;</span>),
        dt.datetime.strftime(<span class="st">&quot;%Z&quot;</span>)
    )


<span class="kw">def</span> logappend(fpath,dt,sender,msg):
    logging.debug(<span class="st">&#39;appending log: </span><span class="sc">%s</span><span class="st">&#39;</span> <span class="op">%</span> (fpath))
    <span class="cf">with</span> <span class="bu">open</span>(fpath, <span class="st">&#39;at&#39;</span>) <span class="im">as</span> f:
        f.write(<span class="st">&quot;(</span><span class="sc">%s</span><span class="st">) </span><span class="sc">%s</span><span class="st">: </span><span class="sc">%s</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">%</span> (
        dt.<span class="bu">format</span>(<span class="st">&#39;YYYY-MM-DD HH:mm:ss&#39;</span>),
        sender,
        msg
    ))
    os.utime(fpath, (dt.timestamp, dt.timestamp))
    os.utime(os.path.dirname(fpath), (dt.timestamp, dt.timestamp))


<span class="kw">def</span> logcreate(fpath,contact, dt,account,plugin):
    logging.debug(<span class="st">&#39;creating converted log: </span><span class="sc">%s</span><span class="st">&#39;</span> <span class="op">%</span> (fpath))
    <span class="cf">if</span> <span class="kw">not</span> os.path.exists(fpath):
        <span class="cf">with</span> <span class="bu">open</span>(fpath, <span class="st">&#39;wt&#39;</span>) <span class="im">as</span> f:
            f.write(<span class="st">&quot;Conversation with </span><span class="sc">%s</span><span class="st"> at </span><span class="sc">%s</span><span class="st"> on </span><span class="sc">%s</span><span class="st"> (</span><span class="sc">%s</span><span class="st">)</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">%</span> (
                contact,
                dt.<span class="bu">format</span>(<span class="st">&#39;ddd dd MMM YYYY hh:mm:ss A ZZZ&#39;</span>),
                account,
                plugin
            ))


<span class="kw">def</span> do_facebook(account, logpathbase):
    plugin <span class="op">=</span> <span class="st">&#39;facebook&#39;</span>

    <span class="co"># the source for message data is from a facebook export</span>
    <span class="co">#</span>
    <span class="co"># for the buddy loookup: the  pidgin buddy list xml (blist.xml) has it, but</span>
    <span class="co"># only after the alias was set for every facebook user by hand</span>
    <span class="co"># the file contains lines constructed:</span>
    <span class="co"># UID\tDisplay Nice Name</span>
    <span class="co">#</span>
    lookupf <span class="op">=</span> os.path.expanduser(<span class="st">&#39;~/tmp/facebook_lookup.csv&#39;</span>)
    lookup <span class="op">=</span> {}
    <span class="cf">with</span> <span class="bu">open</span>(lookupf, newline<span class="op">=</span><span class="st">&#39;&#39;</span>) <span class="im">as</span> csvfile:
        reader <span class="op">=</span> csv.reader(csvfile, delimiter<span class="op">=</span><span class="st">&#39;</span><span class="ch">\t</span><span class="st">&#39;</span>)
        <span class="cf">for</span> row <span class="kw">in</span> reader:
            lookup.update({row[<span class="dv">1</span>]: row[<span class="dv">0</span>]})

    <span class="co"># the csv file for the messages is from the Facebook Data export</span>
    <span class="co"># converted with https://pypi.python.org/pypi/fbchat_archive_parser</span>
    <span class="co"># as: fbcap messages.htm -f csv &gt; ~/tmp/facebook-messages.csv</span>
    dataf <span class="op">=</span> os.path.expanduser(<span class="st">&#39;~/tmp/facebook-messages.csv&#39;</span>)
    reader <span class="op">=</span> csv.DictReader(<span class="bu">open</span>(dataf),skipinitialspace<span class="op">=</span><span class="va">True</span>)
    <span class="cf">for</span> row <span class="kw">in</span> reader:
        <span class="co"># skip conversations for now because I don&#39;t have any way of getting</span>
        <span class="co"># the conversation id</span>
        <span class="cf">if</span> <span class="st">&#39;, &#39;</span> <span class="kw">in</span> row[<span class="st">&#39;thread&#39;</span>]:
            <span class="cf">continue</span>

        <span class="co"># the seconds are sometimes missing from the timestamps</span>
        <span class="cf">try</span>:
            dt <span class="op">=</span> arrow.get(row.get(<span class="st">&#39;date&#39;</span>), <span class="st">&#39;YYYY-MM-DDTHH:mmZZ&#39;</span>)
        <span class="cf">except</span>:
            <span class="cf">try</span>:
                dt <span class="op">=</span> arrow.get(row.get(<span class="st">&#39;date&#39;</span>), <span class="st">&#39;YYYY-MM-DDTHH:mm:ssZZ&#39;</span>)
            <span class="cf">except</span>:
                logging.error(<span class="st">&#39;failed to parse entry: </span><span class="sc">%s</span><span class="st">&#39;</span>, row)

        dt <span class="op">=</span> dt.to(<span class="st">&#39;UTC&#39;</span>)
        contact <span class="op">=</span> lookup.get(row.get(<span class="st">&#39;thread&#39;</span>))
        <span class="cf">if</span> <span class="kw">not</span> contact:
            <span class="cf">continue</span>
        msg <span class="op">=</span> row.get(<span class="st">&#39;message&#39;</span>)
        sender <span class="op">=</span> row.get(<span class="st">&#39;sender&#39;</span>)

        fpath <span class="op">=</span> os.path.join(
            logpathbase,
            plugin,
            account,
            contact,
            logfilename(dt, nulltime<span class="op">=</span><span class="va">True</span>)
        )

        <span class="cf">if</span> <span class="kw">not</span> os.path.isdir(os.path.dirname(fpath)):
            os.makedirs(os.path.dirname(fpath))
        logcreate(fpath, contact, dt, account, plugin)
        logappend(fpath, dt, sender, msg)


<span class="kw">def</span> do_zncfixed(znclogs, logpathbase, znctz):
    <span class="co"># I manually organised the ZNC logs into pidgin-like</span>
    <span class="co"># plugin/account/contact/logfiles.log</span>
    <span class="co"># structure before parsing them</span>
    LINESPLIT <span class="op">=</span> re.<span class="bu">compile</span>(
        <span class="vs">r&#39;^\[(?P&lt;hour&gt;[0-9]+):(?P&lt;minute&gt;[0-9]+):(?P&lt;second&gt;[0-9]+)\]\s+&#39;</span>
        <span class="vs">r&#39;&lt;(?P&lt;sender&gt;.*?)&gt;\s+(?P&lt;msg&gt;.*)$&#39;</span>
    )
    searchin <span class="op">=</span> os.path.join(
        znclogs,
        <span class="st">&#39;**&#39;</span>,
        <span class="st">&#39;*.log&#39;</span>
    )
    logs <span class="op">=</span> glob.glob(searchin, recursive<span class="op">=</span><span class="va">True</span>)
    <span class="cf">for</span> log <span class="kw">in</span> logs:
        contact <span class="op">=</span> os.path.basename(os.path.dirname(log))
        account <span class="op">=</span> os.path.basename(os.path.dirname(os.path.dirname(log)))
        plugin <span class="op">=</span> os.path.basename(os.path.dirname(os.path.dirname(os.path.dirname(log))))
        logging.info(<span class="st">&#39;converting log file: </span><span class="sc">%s</span><span class="st">&#39;</span> <span class="op">%</span> (log))
        dt <span class="op">=</span> arrow.get(os.path.basename(log).replace(<span class="st">&#39;.log&#39;</span>, <span class="st">&#39;&#39;</span>), <span class="st">&#39;YYYY-MM-DD&#39;</span>)
        dt <span class="op">=</span> dt.replace(tzinfo<span class="op">=</span>znctz)


        <span class="cf">if</span> contact.startswith(<span class="st">&quot;#&quot;</span>):
            fname <span class="op">=</span> <span class="st">&quot;</span><span class="sc">%s</span><span class="st">.chat&quot;</span> <span class="op">%</span> (contact)
        <span class="cf">else</span>:
            fname <span class="op">=</span> contact

        fpath <span class="op">=</span> os.path.join(
            logpathbase,
            plugin,
            account,
            fname,
            logfilename(dt)
        )

        <span class="cf">if</span> <span class="kw">not</span> os.path.isdir(os.path.dirname(fpath)):
            os.makedirs(os.path.dirname(fpath))

        <span class="cf">with</span> <span class="bu">open</span>(log, <span class="st">&#39;rb&#39;</span>) <span class="im">as</span> f:
            <span class="cf">for</span> line <span class="kw">in</span> f:
                line <span class="op">=</span> line.decode(<span class="st">&#39;utf8&#39;</span>, <span class="st">&#39;ignore&#39;</span>)
                match <span class="op">=</span> LINESPLIT.match(line)
                <span class="cf">if</span> <span class="kw">not</span> match:
                    <span class="cf">continue</span>
                dt <span class="op">=</span> dt.replace(
                    hour<span class="op">=</span><span class="bu">int</span>(match.group(<span class="st">&#39;hour&#39;</span>)),
                    minute<span class="op">=</span><span class="bu">int</span>(match.group(<span class="st">&#39;minute&#39;</span>)),
                    second<span class="op">=</span><span class="bu">int</span>(match.group(<span class="st">&#39;second&#39;</span>))
                )
                logcreate(fpath, contact, dt, account, plugin)
                logappend(fpath, dt, match.group(<span class="st">&#39;sender&#39;</span>), match.group(<span class="st">&#39;msg&#39;</span>))


<span class="kw">def</span> do_msnplus(msgpluslogs, logpathbase, msgplustz):
    NOPAR <span class="op">=</span> re.<span class="bu">compile</span>(<span class="vs">r&#39;\((.*)\)&#39;</span>)
    NOCOLON <span class="op">=</span> re.<span class="bu">compile</span>(<span class="vs">r&#39;(.*):?&#39;</span>)

    searchin <span class="op">=</span> os.path.join(
        msgpluslogs,
        <span class="st">&#39;**&#39;</span>,
        <span class="st">&#39;*.html&#39;</span>
    )
    logs <span class="op">=</span> glob.glob(searchin, recursive<span class="op">=</span><span class="va">True</span>)
    plugin <span class="op">=</span> <span class="st">&#39;msn&#39;</span>
    <span class="cf">for</span> log <span class="kw">in</span> logs:
        logging.info(<span class="st">&#39;converting log file: </span><span class="sc">%s</span><span class="st">&#39;</span> <span class="op">%</span> (log))
        contact <span class="op">=</span> os.path.basename(os.path.dirname(log))

        <span class="cf">with</span> <span class="bu">open</span>(log, <span class="st">&#39;rt&#39;</span>, encoding<span class="op">=</span><span class="st">&#39;UTF-16&#39;</span>) <span class="im">as</span> f:
            html <span class="op">=</span> BeautifulSoup(f.read(), <span class="st">&quot;html.parser&quot;</span>)
            account <span class="op">=</span> html.find_all(<span class="st">&#39;li&#39;</span>, attrs<span class="op">=</span>{<span class="st">&#39;class&#39;</span>:<span class="st">&#39;in&#39;</span>}, limit<span class="op">=</span><span class="dv">1</span>)[<span class="dv">0</span>]
            account <span class="op">=</span> NOPAR.sub(<span class="st">&#39;\g&lt;1&gt;&#39;</span>, account.span.string)
            <span class="cf">for</span> session <span class="kw">in</span> html.findAll(attrs<span class="op">=</span>{<span class="st">&#39;class&#39;</span>: <span class="st">&#39;mplsession&#39;</span>}):
                dt <span class="op">=</span> arrow.get(
                    session.get(<span class="st">&#39;id&#39;</span>).replace(<span class="st">&#39;Session_&#39;</span>, <span class="st">&#39;&#39;</span>),
                    <span class="st">&#39;YYYY-MM-DDTHH-mm-ss&#39;</span>
                )
                dt <span class="op">=</span> dt.replace(tzinfo<span class="op">=</span>msgplustz)
                seconds <span class="op">=</span> <span class="bu">int</span>(dt.<span class="bu">format</span>(<span class="st">&#39;s&#39;</span>))

                fpath <span class="op">=</span> os.path.join(
                    logpathbase,
                    plugin,
                    account,
                    contact,
                    logfilename(dt)
                )

                <span class="cf">if</span> <span class="kw">not</span> os.path.isdir(os.path.dirname(fpath)):
                    os.makedirs(os.path.dirname(fpath))

                <span class="cf">for</span> line <span class="kw">in</span> session.findAll(<span class="st">&#39;tr&#39;</span>):
                    <span class="cf">if</span> seconds <span class="op">==</span> <span class="dv">59</span>:
                        seconds <span class="op">=</span> <span class="dv">0</span>
                    <span class="cf">else</span>:
                        seconds <span class="op">=</span> seconds <span class="op">+</span> <span class="dv">1</span>

                    tspan <span class="op">=</span> line.find(attrs<span class="op">=</span>{<span class="st">&#39;class&#39;</span>: <span class="st">&#39;time&#39;</span>}).extract()
                    time <span class="op">=</span> tspan.string.replace(<span class="st">&#39;(&#39;</span>, <span class="st">&#39;&#39;</span>).replace(<span class="st">&#39;)&#39;</span>,<span class="st">&#39;&#39;</span>).strip().split(<span class="st">&#39;:&#39;</span>)

                    sender <span class="op">=</span> line.find(<span class="st">&#39;th&#39;</span>).string
                    <span class="cf">if</span> <span class="kw">not</span> sender:
                        <span class="cf">continue</span>

                    sender <span class="op">=</span> sender.strip().split(<span class="st">&#39;:&#39;</span>)[<span class="dv">0</span>]
                    msg <span class="op">=</span> line.find(<span class="st">&#39;td&#39;</span>).get_text()

                    mindt <span class="op">=</span> dt.replace(
                        hour<span class="op">=</span><span class="bu">int</span>(time[<span class="dv">0</span>]),
                        minute<span class="op">=</span><span class="bu">int</span>(time[<span class="dv">1</span>]),
                        second<span class="op">=</span><span class="bu">int</span>(seconds)
                    )

                    logcreate(fpath, contact, dt, account, plugin)
                    logappend(fpath, mindt, sender, msg)


<span class="kw">def</span> do_trillian(trillianlogs, logpathbase, trilliantz):
    SPLIT_SESSIONS <span class="op">=</span> re.<span class="bu">compile</span>(
        <span class="vs">r&#39;^Session Start\s+\((?P&lt;participants&gt;.*)?\):\s+(?P&lt;timestamp&gt;[^\n]+)&#39;</span>
        <span class="vs">r&#39;\n(?P&lt;session&gt;(?:.|\n)*?)(?=Session)&#39;</span>,
        re.MULTILINE
    )

    SPLIT_MESSAGES <span class="op">=</span> re.<span class="bu">compile</span>(
        <span class="vs">r&#39;\[(?P&lt;time&gt;[^\]]+)\]\s+(?P&lt;sender&gt;.*?):\s+&#39;</span>
        <span class="vs">r&#39;(?P&lt;msg&gt;(?:.|\n)*?)(?=\n\[|$)&#39;</span>
    )

    searchin <span class="op">=</span> os.path.join(
        trillianlogs,
        <span class="st">&#39;**&#39;</span>,
        <span class="st">&#39;*.log&#39;</span>
    )

    logs <span class="op">=</span> glob.glob(searchin, recursive<span class="op">=</span><span class="va">True</span>)
    <span class="cf">for</span> log <span class="kw">in</span> logs:
        <span class="cf">if</span> <span class="st">&#39;Channel&#39;</span> <span class="kw">in</span> log:
            logging.warn(
                <span class="st">&quot;Group conversations are not supported yet, skipping </span><span class="sc">%s</span><span class="st">&quot;</span> <span class="op">%</span> log
            )
            <span class="cf">continue</span>

        logging.info(<span class="st">&#39;converting log file: </span><span class="sc">%s</span><span class="st">&#39;</span> <span class="op">%</span> (log))
        contact <span class="op">=</span> os.path.basename(log).replace(<span class="st">&#39;.log&#39;</span>, <span class="st">&#39;&#39;</span>)
        plugin <span class="op">=</span> os.path.basename(os.path.dirname(os.path.dirname(log))).lower()

        <span class="cf">with</span> <span class="bu">open</span>(log, <span class="st">&#39;rb&#39;</span>) <span class="im">as</span> f:
            c <span class="op">=</span> f.read().decode(<span class="st">&#39;utf8&#39;</span>, <span class="st">&#39;ignore&#39;</span>)

            <span class="cf">for</span> session <span class="kw">in</span> SPLIT_SESSIONS.findall(c):
                participants, timestamp, session <span class="op">=</span> session
                logging.debug(<span class="st">&#39;converting session starting at: </span><span class="sc">%s</span><span class="st">&#39;</span> <span class="op">%</span> (timestamp))
                participants <span class="op">=</span> participants.split(<span class="st">&#39;:&#39;</span>)
                account <span class="op">=</span> participants[<span class="dv">0</span>]
                dt <span class="op">=</span> arrow.get(timestamp, <span class="st">&#39;ddd MMM DD HH:mm:ss YYYY&#39;</span>)
                dt <span class="op">=</span> dt.replace(tzinfo<span class="op">=</span>trilliantz)
                fpath <span class="op">=</span> os.path.join(
                    logpathbase,
                    plugin,
                    participants[<span class="dv">0</span>],
                    contact,
                    logfilename(dt)
                )

                <span class="cf">if</span> <span class="kw">not</span> os.path.isdir(os.path.dirname(fpath)):
                    os.makedirs(os.path.dirname(fpath))

                seconds <span class="op">=</span> <span class="bu">int</span>(dt.<span class="bu">format</span>(<span class="st">&#39;s&#39;</span>))
                curr_mindt <span class="op">=</span> dt
                <span class="cf">for</span> line <span class="kw">in</span> SPLIT_MESSAGES.findall(session):
                    <span class="co"># this is a fix for ancient trillian logs where seconds</span>
                    <span class="co"># were missing</span>
                    <span class="cf">if</span> seconds <span class="op">==</span> <span class="dv">59</span>:
                        seconds <span class="op">=</span> <span class="dv">0</span>
                    <span class="cf">else</span>:
                        seconds <span class="op">=</span> seconds <span class="op">+</span> <span class="dv">1</span>

                    time, sender, msg <span class="op">=</span> line
                    <span class="cf">try</span>:
                        mindt <span class="op">=</span> arrow.get(time,
                        <span class="st">&#39;YYYY.MM.DD HH:mm:ss&#39;</span>)
                    <span class="cf">except</span>:
                        time <span class="op">=</span> time.split(<span class="st">&#39;:&#39;</span>)
                        mindt <span class="op">=</span> dt.replace(
                            hour<span class="op">=</span><span class="bu">int</span>(time[<span class="dv">0</span>]),
                            minute<span class="op">=</span><span class="bu">int</span>(time[<span class="dv">1</span>]),
                            second<span class="op">=</span><span class="bu">int</span>(seconds)
                        )

                    <span class="co"># creating the filw with the header has to be here to</span>
                    <span class="co"># avoid empty or status-messages only files</span>
                    logcreate(fpath, participants[<span class="dv">1</span>], dt, account, plugin)
                    logappend(fpath, mindt, sender, msg)

            <span class="cf">if</span> params.get(<span class="st">&#39;cleanup&#39;</span>):
                <span class="bu">print</span>(<span class="st">&#39;deleting old log: </span><span class="sc">%s</span><span class="st">&#39;</span> <span class="op">%</span> (log))
                os.unlink(log)


<span class="kw">def</span> do_skype(skypedbpath, logpathbase):
    db <span class="op">=</span> sqlite3.<span class="ex">connect</span>(skypedbpath)

    cursor <span class="op">=</span> db.cursor()
    cursor.execute(<span class="st">&#39;&#39;&#39;SELECT `skypename` from Accounts&#39;&#39;&#39;</span>)
    accounts <span class="op">=</span> cursor.fetchall()
    <span class="cf">for</span> account <span class="kw">in</span> accounts:
        account <span class="op">=</span> account[<span class="dv">0</span>]
        cursor.execute(<span class="st">&#39;&#39;&#39;</span>
<span class="st">        SELECT</span>
<span class="st">            `timestamp`,</span>
<span class="st">            `dialog_partner`,</span>
<span class="st">            `author`,</span>
<span class="st">            `from_dispname`,</span>
<span class="st">            `body_xml`</span>
<span class="st">        FROM</span>
<span class="st">            `Messages`</span>
<span class="st">        WHERE</span>
<span class="st">            `chatname` LIKE ?</span>
<span class="st">        ORDER BY</span>
<span class="st">            `timestamp` ASC</span>
<span class="st">        &#39;&#39;&#39;</span>, (<span class="st">&#39;%&#39;</span> <span class="op">+</span> account <span class="op">+</span> <span class="st">&#39;%&#39;</span>,))

        messages <span class="op">=</span> cursor.fetchall()
        <span class="cf">for</span> r <span class="kw">in</span> messages:
            dt <span class="op">=</span> arrow.get(r[<span class="dv">0</span>])
            dt <span class="op">=</span> dt.replace(tzinfo<span class="op">=</span><span class="st">&#39;UTC&#39;</span>)
            fpath <span class="op">=</span> os.path.join(
                logpathbase,
                account,
                r[<span class="dv">1</span>],
                logfilename(dt, nulltime<span class="op">=</span><span class="va">True</span>)
            )

            <span class="cf">if</span> <span class="kw">not</span> os.path.isdir(os.path.dirname(fpath)):
                os.makedirs(os.path.dirname(fpath))

            logcreate(fpath, r[<span class="dv">1</span>], dt, account, <span class="st">&#39;skype&#39;</span>)
            logappend(fpath, dt, r[<span class="dv">3</span>], r[<span class="dv">4</span>])


<span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&#39;__main__&#39;</span>:
    parser <span class="op">=</span> argparse.ArgumentParser(description<span class="op">=</span><span class="st">&#39;Parameters for Skype v2 logs to Pidgin logs converter&#39;</span>)

    parser.add_argument(
        <span class="st">&#39;--skype_db&#39;</span>,
        default<span class="op">=</span>os.path.expanduser(<span class="st">&#39;~/.skype/main.db&#39;</span>),
        <span class="bu">help</span><span class="op">=</span><span class="st">&#39;absolute path to skype main.db&#39;</span>
    )

    parser.add_argument(
        <span class="st">&#39;--pidgin_logs&#39;</span>,
        default<span class="op">=</span>os.path.expanduser(<span class="st">&#39;~/.purple/logs/skype&#39;</span>),
        <span class="bu">help</span><span class="op">=</span><span class="st">&#39;absolute path to Pidgin skype logs&#39;</span>
    )

    parser.add_argument(
        <span class="st">&#39;--facebook_account&#39;</span>,
        default<span class="op">=</span><span class="st">&#39;&#39;</span>,
        <span class="bu">help</span><span class="op">=</span><span class="st">&#39;facebook account name&#39;</span>
    )

    parser.add_argument(
        <span class="st">&#39;--loglevel&#39;</span>,
        default<span class="op">=</span><span class="st">&#39;warning&#39;</span>,
        <span class="bu">help</span><span class="op">=</span><span class="st">&#39;change loglevel&#39;</span>
    )

    <span class="cf">for</span> allowed <span class="kw">in</span> [<span class="st">&#39;skype&#39;</span>, <span class="st">&#39;trillian&#39;</span>, <span class="st">&#39;msnplus&#39;</span>, <span class="st">&#39;znc&#39;</span>, <span class="st">&#39;facebook&#39;</span>]:
        parser.add_argument(
            <span class="st">&#39;--</span><span class="sc">%s</span><span class="st">&#39;</span> <span class="op">%</span> allowed,
            action<span class="op">=</span><span class="st">&#39;store_true&#39;</span>,
            default<span class="op">=</span><span class="va">False</span>,
            <span class="bu">help</span><span class="op">=</span><span class="st">&#39;convert </span><span class="sc">%s</span><span class="st"> logs&#39;</span> <span class="op">%</span> allowed
        )

        <span class="cf">if</span> allowed <span class="op">!=</span> <span class="st">&#39;skype&#39;</span> <span class="kw">or</span> allowed <span class="op">!=</span> <span class="st">&#39;facebook&#39;</span>:
            parser.add_argument(
                <span class="st">&#39;--</span><span class="sc">%s</span><span class="st">_logs&#39;</span> <span class="op">%</span> allowed,
                default<span class="op">=</span>os.path.expanduser(<span class="st">&#39;~/.</span><span class="sc">%s</span><span class="st">/logs&#39;</span> <span class="op">%</span> allowed),
                <span class="bu">help</span><span class="op">=</span><span class="st">&#39;absolute path to </span><span class="sc">%s</span><span class="st"> logs&#39;</span> <span class="op">%</span> allowed
            )

            parser.add_argument(
                <span class="st">&#39;--</span><span class="sc">%s</span><span class="st">_timezone&#39;</span> <span class="op">%</span> allowed,
                default<span class="op">=</span><span class="st">&#39;UTC&#39;</span>,
                <span class="bu">help</span><span class="op">=</span><span class="st">&#39;timezone name for </span><span class="sc">%s</span><span class="st"> logs (eg. US/Pacific)&#39;</span> <span class="op">%</span> allowed
            )

    params <span class="op">=</span> <span class="bu">vars</span>(parser.parse_args())

    <span class="co"># remove the rest of the potential loggers</span>
    <span class="cf">while</span> <span class="bu">len</span>(logging.root.handlers) <span class="op">&gt;</span> <span class="dv">0</span>:
        logging.root.removeHandler(logging.root.handlers[<span class="op">-</span><span class="dv">1</span>])

    LLEVEL <span class="op">=</span> {
        <span class="st">&#39;critical&#39;</span>: <span class="dv">50</span>,
        <span class="st">&#39;error&#39;</span>: <span class="dv">40</span>,
        <span class="st">&#39;warning&#39;</span>: <span class="dv">30</span>,
        <span class="st">&#39;info&#39;</span>: <span class="dv">20</span>,
        <span class="st">&#39;debug&#39;</span>: <span class="dv">10</span>
    }

    logging.basicConfig(
        level<span class="op">=</span>LLEVEL[params.get(<span class="st">&#39;loglevel&#39;</span>)],
        <span class="bu">format</span><span class="op">=</span><span class="st">&#39;</span><span class="sc">%(asctime)s</span><span class="st"> - </span><span class="sc">%(levelname)s</span><span class="st"> - </span><span class="sc">%(message)s</span><span class="st">&#39;</span>
    )

    <span class="cf">if</span> params.get(<span class="st">&#39;facebook&#39;</span>):
        logging.info(<span class="st">&#39;facebook enabled&#39;</span>)
        do_facebook(
            params.get(<span class="st">&#39;facebook_account&#39;</span>),
            params.get(<span class="st">&#39;pidgin_logs&#39;</span>)
        )


    <span class="cf">if</span> params.get(<span class="st">&#39;skype&#39;</span>):
        logging.info(<span class="st">&#39;Skype enabled; parsing skype logs&#39;</span>)
        do_skype(
            params.get(<span class="st">&#39;skype_db&#39;</span>),
            params.get(<span class="st">&#39;pidgin_logs&#39;</span>)
        )

    <span class="cf">if</span> params.get(<span class="st">&#39;trillian&#39;</span>):
        logging.info(<span class="st">&#39;Trillian enabled; parsing trillian logs&#39;</span>)
        do_trillian(
            params.get(<span class="st">&#39;trillian_logs&#39;</span>),
            params.get(<span class="st">&#39;pidgin_logs&#39;</span>),
            params.get(<span class="st">&#39;trillian_timezone&#39;</span>),
        )

    <span class="cf">if</span> params.get(<span class="st">&#39;msnplus&#39;</span>):
        logging.info(<span class="st">&#39;MSN Plus! enabled; parsing logs&#39;</span>)
        do_msnplus(
            params.get(<span class="st">&#39;msnplus_logs&#39;</span>),
            params.get(<span class="st">&#39;pidgin_logs&#39;</span>),
            params.get(<span class="st">&#39;msnplus_timezone&#39;</span>),
        )

    <span class="cf">if</span> params.get(<span class="st">&#39;znc&#39;</span>):
        logging.info(<span class="st">&#39;ZNC enabled; parsing znc logs&#39;</span>)
        do_zncfixed(
            params.get(<span class="st">&#39;znc_logs&#39;</span>),
            params.get(<span class="st">&#39;pidgin_logs&#39;</span>),
            params.get(<span class="st">&#39;znc_timezone&#39;</span>),
        )</code></pre></div>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p><a href="http://www.irc.org/" class="uri">http://www.irc.org/</a><a href="#fnref1">↩</a></p></li>
<li id="fn2"><p><a href="https://www.mirc.com/" class="uri">https://www.mirc.com/</a><a href="#fnref2">↩</a></p></li>
<li id="fn3"><p><a href="https://icq.com/" class="uri">https://icq.com/</a><a href="#fnref3">↩</a></p></li>
<li id="fn4"><p><a href="https://medium.com/@Dimitryophoto/icq-20-years-is-no-limit-8734e1eea8ea" class="uri">https://medium.com/@Dimitryophoto/icq-20-years-is-no-limit-8734e1eea8ea</a><a href="#fnref4">↩</a></p></li>
<li id="fn5"><p><a href="https://en.wikipedia.org/wiki/Windows_Live_Messenger" class="uri">https://en.wikipedia.org/wiki/Windows_Live_Messenger</a><a href="#fnref5">↩</a></p></li>
<li id="fn6"><p><a href="https://www.trillian.im/" class="uri">https://www.trillian.im/</a><a href="#fnref6">↩</a></p></li>
<li id="fn7"><p><a href="http://pidgin.im/" class="uri">http://pidgin.im/</a><a href="#fnref7">↩</a></p></li>
<li id="fn8"><p><a href="https://xmpp.org/" class="uri">https://xmpp.org/</a><a href="#fnref8">↩</a></p></li>
<li id="fn9"><p><a href="https://xmpp.org/extensions/xep-0280.html" class="uri">https://xmpp.org/extensions/xep-0280.html</a><a href="#fnref9">↩</a></p></li>
<li id="fn10"><p><a href="https://en.wikipedia.org/wiki/Whatsapp" class="uri">https://en.wikipedia.org/wiki/Whatsapp</a><a href="#fnref10">↩</a></p></li>
<li id="fn11"><p><a href="https://en.wikipedia.org/wiki/BlackBerry_Messenger" class="uri">https://en.wikipedia.org/wiki/BlackBerry_Messenger</a><a href="#fnref11">↩</a></p></li>
<li id="fn12"><p><a href="https://en.wikipedia.org/wiki/Google_talk" class="uri">https://en.wikipedia.org/wiki/Google_talk</a><a href="#fnref12">↩</a></p></li>
<li id="fn13"><p><a href="https://en.wikipedia.org/wiki/Google_Hangouts" class="uri">https://en.wikipedia.org/wiki/Google_Hangouts</a><a href="#fnref13">↩</a></p></li>
<li id="fn14"><p><a href="https://en.wikipedia.org/wiki/MQTT" class="uri">https://en.wikipedia.org/wiki/MQTT</a><a href="#fnref14">↩</a></p></li>
<li id="fn15"><p><a href="http://www.salimvirani.com/facebook/" class="uri">http://www.salimvirani.com/facebook/</a><a href="#fnref15">↩</a></p></li>
<li id="fn16"><p><a href="https://www.facebook.com/workplace" class="uri">https://www.facebook.com/workplace</a><a href="#fnref16">↩</a></p></li>
<li id="fn17"><p><a href="https://www.blog.google/products/g-suite/move-projects-forward-one-placehangouts-chat-now-available/" class="uri">https://www.blog.google/products/g-suite/move-projects-forward-one-placehangouts-chat-now-available/</a><a href="#fnref17">↩</a></p></li>
<li id="fn18"><p><a href="https://telegram.org/" class="uri">https://telegram.org/</a><a href="#fnref18">↩</a></p></li>
<li id="fn19"><p><a href="https://xmpp.org/extensions/xep-0384.html" class="uri">https://xmpp.org/extensions/xep-0384.html</a><a href="#fnref19">↩</a></p></li>
<li id="fn20"><p><a href="https://matrix.org/" class="uri">https://matrix.org/</a><a href="#fnref20">↩</a></p></li>
<li id="fn21"><p><a href="https://xmpp.org/extensions/xep-0384.html" class="uri">https://xmpp.org/extensions/xep-0384.html</a><a href="#fnref21">↩</a></p></li>
<li id="fn22"><p><a href="https://xmpp.org/extensions/xep-0166.html" class="uri">https://xmpp.org/extensions/xep-0166.html</a><a href="#fnref22">↩</a></p></li>
<li id="fn23"><p><a href="https://play.google.com/store/apps/details?id=com.mailsite.astrachat" class="uri">https://play.google.com/store/apps/details?id=com.mailsite.astrachat</a><a href="#fnref23">↩</a></p></li>
<li id="fn24"><p><a href="https://f-droid.org/packages/eu.siacs.conversations/" class="uri">https://f-droid.org/packages/eu.siacs.conversations/</a><a href="#fnref24">↩</a></p></li>
<li id="fn25"><p><a href="https://www.linphone.org/" class="uri">https://www.linphone.org/</a><a href="#fnref25">↩</a></p></li>
<li id="fn26"><p><a href="https://bitbucket.org/EionRobb/purple-hangouts/src#markdown-header-compiling" class="uri">https://bitbucket.org/EionRobb/purple-hangouts/src#markdown-header-compiling</a><a href="#fnref26">↩</a></p></li>
<li id="fn27"><p><a href="https://github.com/dequis/purple-facebook/issues/371" class="uri">https://github.com/dequis/purple-facebook/issues/371</a><a href="#fnref27">↩</a></p></li>
<li id="fn28"><p><a href="http://repo.xposed.info/" class="uri">http://repo.xposed.info/</a><a href="#fnref28">↩</a></p></li>
<li id="fn29"><p><a href="https://lua.xprivacy.eu/" class="uri">https://lua.xprivacy.eu/</a><a href="#fnref29">↩</a></p></li>
<li id="fn30"><p><a href="https://developer.pidgin.im/wiki/ThirdPartyPlugins" class="uri">https://developer.pidgin.im/wiki/ThirdPartyPlugins</a><a href="#fnref30">↩</a></p></li>
<li id="fn31"><p><a href="https://bitbucket.org/rekkanoryo/purple-plugin-pack/" class="uri">https://bitbucket.org/rekkanoryo/purple-plugin-pack/</a><a href="#fnref31">↩</a></p></li>
<li id="fn32"><p><a href="https://xmpp.org/extensions/xep-0280.html" class="uri">https://xmpp.org/extensions/xep-0280.html</a><a href="#fnref32">↩</a></p></li>
<li id="fn33"><p><a href="https://xmpp.org/extensions/xep-0384.html" class="uri">https://xmpp.org/extensions/xep-0384.html</a><a href="#fnref33">↩</a></p></li>
<li id="fn34"><p><a href="https://f-droid.org/packages/eu.siacs.conversations/" class="uri">https://f-droid.org/packages/eu.siacs.conversations/</a><a href="#fnref34">↩</a></p></li>
<li id="fn35"><p><a href="https://xmpp.org/extensions/xep-0184.html" class="uri">https://xmpp.org/extensions/xep-0184.html</a><a href="#fnref35">↩</a></p></li>
</ol>
</section>]]></content>
    <link href="https://petermolnar.net/instant-messenger-hell/" rel="alternate"/>
    <published>2018-03-03T15:00:00+00:00</published>
    <rights>2018 Peter Molnar CC BY 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/odexed-android-6-marshmallow-with-microg</id>
    <title>How to install microG an odexed stock android ROM</title>
    <updated>2018-02-24T10:00:00+00:00</updated>
    <content type="CDATA"><![CDATA[<h2 id="why-would-anyone-want-an-android-without-google">Why would anyone want an android without Google?</h2>
<p>About 1.5 years ago I attended to a wedding. It took place outside of the city, at a restaurant with a very nice garden, where I've never before. In about 2 hours into the happening, my phone buzzed. I took it out, expecting a text message, or similar, but no: it was Google Maps, asking me if I'm at the place where I am, and since I'm there, could I upload some pictures of the place?</p>
<p>Since then this had become regular, until the point it became obstructive and annoying. I'm not alone: Brad Frost's<a href="#fn1" class="footnoteRef" id="fnref1"><sup>1</sup></a> entry talks about the same problem. I've tried everything to turn Google location tracking off. I went to Location History<a href="#fn2" class="footnoteRef" id="fnref2"><sup>2</sup></a> and disabled it. I went through the Google Maps application and removed every notification. This latter one cleared the madness for a while - until a new version of Maps came up, which introduced some extra notification setting, which then showed yet another popup out of the blue.</p>
<p>Google fails to respect user settings lately, turning into a desperately annoying data hoarding monster. They've been doing nasty things, like silently collecting cell tower information, even with location settings disabled for years<a href="#fn3" class="footnoteRef" id="fnref3"><sup>3</sup></a>, but with the coming of GDPR<a href="#fn4" class="footnoteRef" id="fnref4"><sup>4</sup></a> they need to get consent - hence the silly amount of notifications they are bombarding you with.</p>
<p>Once I set a setting in a service, I expect it to stay they way I set it. I expect backwards compatibility, backfilled data, if needed. Google and Facebook are both failing at this; Facebook always had, Google only recently started. <em>New app, we renamed all the settings, let's reset them to the default level of annoyance!</em></p>
<p>The whole problem on android can be traced back to one omnipotent application: Google Services Framework. This silent, invisible beast upgrades itself and Play Store whenever, wherever it wants it to. It does this all in the background, not even letting you know. If you happen to run an ancient phone, like my HTC Desire<a href="#fn5" class="footnoteRef" id="fnref5"><sup>5</sup></a>, it will fill up that generous 150MB you have for user apps without a blink of an eye, and let you wonder why your phone can't boot any more.</p>
<p>The extremely sad part is that everyone started depending on GMS - Google Mobile Services - for convenience: it provides push services, you don't have to run you own. It all leads to the point that android, while in theory is Open Source, it will never be Free from Google, in it's current form.</p>
<p>Enter microG<a href="#fn6" class="footnoteRef" id="fnref6"><sup>6</sup></a>: a few enthusiasts with the same feelings as me, but with actual results. microG is a service level replacement for Google; it's Free and Open Source, it's transparent. There's only one problem: it's <em>very</em> tricky to install it on nieche phones, with odexed ROMs.</p>
<p>So I made a guide. While this guide was made using a Nomu S10<a href="#fn7" class="footnoteRef" id="fnref7"><sup>7</sup></a>, but given it's an AOSP based ROM with tiny modifications, I'm fairly certain it can be applied to any similar, <del>no-name</del> less known brands and phones.</p>
<h2 id="important-notes">Important notes</h2>
<p><strong>The methods below might void your warranty. It might brick your phone. It will take while. It can cause an unexpected amount of hair pulled out.</strong></p>
<p><strong>Never do it on your only phone, or a phone you value high. I take no responsibility if anything goes wrong.</strong></p>
<p><strong>It was done on a Nomu S10, with Android 6.0 Marshmallow. It will most probably need to be altered for other versions.</strong></p>
<p><strong>The heavy lifting, the work, are all done by magnificent people out there; this article is merely summary of existing knowledge.</strong></p>
<p>The only thing I can assure is that it worked for me, but it took a weekend to get to the bottom of it.</p>
<h2 id="prerequisites">Prerequisites</h2>
<h3 id="operating-system-adb-fastboot">Operating system, adb, fastboot</h3>
<p>The guide was made for <strong>Debian</strong> based linux, including <strong>Ubuntu</strong> and <strong>Mint</strong>.</p>
<p>I assume you have a general understanding and familiarity with <code>fastboot</code> and <code>adb</code> - these are both needed during the process.</p>
<p>It's doable on Windows as well, with very similar steps, but I don't have a Windows, so I can't make that guide.</p>
<h3 id="sp-flash-tool-flashing-stock-roms-on-mediatek-devices">SP Flash Tool (flashing stock ROMs on MediaTek devices)</h3>
<p>The stock ZIPs Nomu provide can't be flashed via the regular recoveries, like TWRP. As a workaround, I used to extract it and flash the pieces by <code>fastboot</code> - that is because I wasn't aware of a tool, called <strong>SP Flash Tool</strong> and the MediaTek download mode.</p>
<p>When the phone is turned off and connected to a computer via USB, it shows up as modem (!) device, as ttyACM. The SP Flash tool uses this to flash the ROM, but in order to do that - even if the flash tool is run by root - needs some tweaking on the linux side.</p>
<p>In order to get this supported on Debian, some udev rules need to be added:</p>
<p>Run (as root):</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">cat</span> <span class="op">&gt;</span> etc/udev/rules.d/20-mediatek-blacklist.rules <span class="op">&lt;&lt; EOF</span>
ATTRS{idVendor}==&quot;0e8d&quot;, ENV{ID_MM_DEVICE_IGNORE}=&quot;1&quot;
ATTRS{idVendor}==&quot;6000&quot;, ENV{ID_MM_DEVICE_IGNORE}=&quot;1&quot;
EOF

cat &gt; etc/udev/rules.d/80-mediatek-usb.rules &lt;&lt; EOF
SUBSYSTEM==&quot;usb&quot;, ACTION==&quot;add&quot;, ATTR{idVendor}==&quot;0e8d&quot;, ATTR{idProduct}==&quot;*&quot;
EOF

systemctl restart udev.service</code></pre></div>
<p>Once done add your user to the <code>dialout</code> and <code>uucp</code> groups as:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">usermod</span> -a -G dialout,uucp YOUR_USERNAME</code></pre></div>
<p>Sp Flash tool needs an old version of libpng12, so get that from the Debian packages, or from the jessie (oldstable) repository:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">wget</span> http://ftp.uk.debian.org/debian/pool/main/libp/libpng/libpng12-0_1.2.50-2+deb8u3_amd64.deb
<span class="ex">dpkg</span> -i libpng12-0_1.2.50-2+deb8u3_amd64.deb
<span class="fu">rm</span> libpng12-0_1.2.50-2+deb8u3_amd64.deb</code></pre></div>
<p>This should make it possible to flash, using the SP flash tool, which can be downloaded from spflashtool.com<a href="#fn8" class="footnoteRef" id="fnref8"><sup>8</sup></a>.</p>
<p>Credit due to Miss Montage on needrom.com<a href="#fn9" class="footnoteRef" id="fnref9"><sup>9</sup></a> for finding these out.</p>
<h3 id="twrp-recovery-for-nomu-s10">TWRP recovery for Nomu S10</h3>
<p><strong>Do not flash TWRP recovery on the Nomu S10</strong>. There is some kind of safety check, which wants to trigger a factory reset, so what happens in short is the following: - you flash system, boot, cache from stock - you flash TWRP as recovery - the phone boots - it re-locks the OEM unlock switch - it removes TWRP</p>
<p>At this point it's stuck with a boot logo on screen (not even a bootloop); it can't even be turned off, it's without a working system, no recovery, and a locked fastboot. If you reach this point, use SP Flash Tool described above.</p>
<p><strong>Instead of flashing, a method called dirty boot will be used</strong>: via <code>fastboot</code>, the TWRP image will be booted from the PC, not from the phone, because TWRP is still neede in order to flash custom ZIPs. Jemmini<a href="#fn10" class="footnoteRef" id="fnref10"><sup>10</sup></a> made one for the Nomu S10; it in a zip<a href="#fn11" class="footnoteRef" id="fnref11"><sup>11</sup></a> ; extract it to get a <code>.img</code> file.</p>
<h3 id="supersu-flashable-zip">SuperSu flashable ZIP</h3>
<p>Many things have changed since the early days of rooting android (2.3 and 4.x). Nowadays fiddling with the system /bin might result in mayhem, so clever people came up with the idea to put <code>su</code>, the actual binary needed for rooting into the boot image, ending up in <code>/sbin</code>, without triggering any &quot;security&quot;. One of these systemless rooting apps is SuperSu<a href="#fn12" class="footnoteRef" id="fnref12"><sup>12</sup></a>, which you'll need in a flashable zip<a href="#fn13" class="footnoteRef" id="fnref13"><sup>13</sup></a> format.</p>
<h3 id="xposed-framework-zip-and-apk">Xposed framework ZIP and apk</h3>
<p>A vast amount of Android's real potential is lock behind bars - the reason is &quot;security&quot;. I'm putting this in quotes, because it's smoke and mirrors: malware can use vulnerabilities to install itself deep inside the system that it's impossible to even detect it, yet you're not allowed to have full access on your phone. Not even the security suits and the malware scanners ask for root, and without that, they are not much more than a paid, bad joke.</p>
<p>Anyway: the XPosed Framework<a href="#fn14" class="footnoteRef" id="fnref14"><sup>14</sup></a> is here to help. It's a utility which lets you install modules which modules tweak low level android behaviour. For example, Volumesteps+ will let you change the granularity of volume steps, which is very useful for someone who'd find a volume level between the factory 8 and 9 the best. For us, the important module will be FakeGApps, which allows signature spoofing<a href="#fn15" class="footnoteRef" id="fnref15"><sup>15</sup></a>, which is a hard requirement for microG to work.</p>
<p>For reasons I'm yet to understand, I had to both flash the zip<a href="#fn16" class="footnoteRef" id="fnref16"><sup>16</sup></a> and install the apk<a href="#fn17" class="footnoteRef" id="fnref17"><sup>17</sup></a> version to get XPosed on the phone. In theory, only one should have been enough, but for now, get both.</p>
<h3 id="nanodroid-microg-zip">NanoDroid microG ZIP</h3>
<p>NanoDroid<a href="#fn18" class="footnoteRef" id="fnref18"><sup>18</sup></a> is originally a Magisk module (it's another systemless rooting and framework, but I could never get it running on the Nomu), but it's also available as flashable ZIPs.</p>
<p>For our needs, only the NanoDroid microG zip<a href="#fn19" class="footnoteRef" id="fnref19"><sup>19</sup></a> is required.</p>
<h2 id="the-actual-un-googling">The actual un-googling</h2>
<h3 id="summary">Summary</h3>
<ol type="1">
<li>root the phone and install xposed</li>
<li>enable signature spoofing via xposed module fakegapps</li>
<li>removed all google related apps, libs, entries</li>
<li>install microg</li>
</ol>
<p>Now: in details.</p>
<h3 id="rooting-the-nomu-s10-with-supersu-and-xposed">1. Rooting the Nomu S10 with SuperSu and Xposed</h3>
<p><strong>Important: OEM unlocking will trigger a factory reset, it will wipe every user and app setting from the phone.</strong></p>
<ol type="1">
<li>Start android</li>
<li>Enable <code>Developer Options</code>:
<ul>
<li>go into <code>Settings</code></li>
<li><code>About Phone</code></li>
<li>tap on the <code>Build Number</code> until you get the message 'you are now a developer!'
<figure class="photo">
<a href="https://petermolnar.net/files/nomu_s10_ungoogle_buildnumber_z.png" class=""> <img src="https://petermolnar.net/files/nomu_s10_ungoogle_buildnumber_z.png" title="" alt="Build number in About Phone" class="adaptimg" width="640" height="140" /> </a>
<figcaption>
Build number in About Phone
</figcaption>
</figure></li>
</ul></li>
<li>Enable OEM unlocking
<ul>
<li>go into <code>Settings</code></li>
<li><code>Developer</code></li>
<li>allow <code>oem unlocking</code></li>
</ul></li>
<li>Enable USB Debugging (ADB)
<ul>
<li>go into <code>Settings</code></li>
<li><code>Developer</code></li>
<li>allow <code>usb debugging</code>
<figure class="photo">
<a href="https://petermolnar.net/files/nomu_s10_ungoogle_oemunlock_z.png" class=""> <img src="https://petermolnar.net/files/nomu_s10_ungoogle_oemunlock_z.png" title="" alt="OEM unlock and USB debugging options" class="adaptimg" width="640" height="489" /> </a>
<figcaption>
OEM unlock and USB debugging options
</figcaption>
</figure></li>
</ul></li>
<li>reboot into fastboot:
<ul>
<li>run <code>adb reboot bootloader</code> from the host, <strong>or</strong>:</li>
<li>power off the phone, then press and hold <code>Volume Up</code> and <code>Power</code>
<figure class="photo"></li>
</ul></li>
</ol>
<p><img src="/files/nomu_s10_ungoogle_fastboot_menu.png" title="" alt="Fastboot menu" class="adaptimg" width="560" height="94" /></p>
<figcaption>
Fastboot menu
</figcaption>
</figure>
<ol start="6" type="1">
<li>in fastboot, issue the oem unlock command:
<ul>
<li><code>fastboot oem unlock</code> from the host</li>
<li>press <code>Volume up</code> as asked on the phones
<figure class="photo"></li>
</ul></li>
</ol>
<p><img src="/files/nomu_s10_ungoogle_fastboot_prompt.png" title="" alt="Fastboot menu" class="adaptimg" width="560" height="19" /></p>
<figcaption>
Fastboot menu
</figcaption>
</figure>
<ol start="7" type="1">
<li>reboot the phone: <code>fastboot reboot</code></li>
<li>let the factory reset flush through</li>
<li>re-enable <code>Developer Options</code> (see 2.)</li>
<li>verify that OEM unlocking is on - is should be. If not, go back to 1. and start again.</li>
<li>re-enable <code>USB debugging</code> (see 3.)</li>
<li>boot into fastboot (see 5.)</li>
<li>&quot;dirty&quot; boot TWRP recovery:
<ul>
<li><code>fastboot boot [path to the twrp image - the one extracted from the zip]</code></li>
</ul></li>
<li>install SuperSu via TWRP
<ul>
<li>either put the zips on a microSD card and flash it as regular zips</li>
<li>or use <code>adb sideload</code> from the <code>Advanced</code> menu in TWRP</li>
</ul></li>
<li>install Xposed via TWRP (see 14.)</li>
<li>reboot the phone</li>
<li>install the Xposed apk</li>
</ol>
<h3 id="enable-signature-spoofing-with-fakegapps">2. Enable signature spoofing with FakeGapps</h3>
<p>Once the Xposed Installer is up and running it will look like this:</p>
<figure class="photo">
<a href="https://petermolnar.net/files/nomu_s10_ungoogle_xposed_b.png" class=""> <img src="https://petermolnar.net/files/nomu_s10_ungoogle_xposed_z.png" title="" alt="Running and enabled Xposed" class="adaptimg" width="405" height="640" /> </a>
<figcaption>
Running and enabled Xposed
</figcaption>
</figure>
<p>Under <code>Menu</code>, <code>Download</code> search for FakeGApps<a href="#fn20" class="footnoteRef" id="fnref20"><sup>20</sup></a>, click <code>Download</code> and enable it:</p>
<figure class="photo">
<a href="https://petermolnar.net/files/nomu_s10_ungoogle_fakegapps_xposed_z.png" class=""> <img src="https://petermolnar.net/files/nomu_s10_ungoogle_fakegapps_xposed_z.png" title="" alt="FakeGApps in Xposed" class="adaptimg" width="640" height="496" /> </a>
<figcaption>
FakeGApps in Xposed
</figcaption>
</figure>
<p><strong>For FakeGApps to take effect, the phone has to be rebooted, but it can be done together with the next step.</strong></p>
<h3 id="remove-all-google-apps-and-libraries">3. Remove all google apps and libraries</h3>
<p>These commands should be run either via an <code>adb</code> shell, or from a <code>Terminal Emulator</code> on the phone.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">adb</span> shell

<span class="co"># become root - SuperSu will prompt for verification</span>
<span class="fu">su</span> -
<span class="co"># remount / and /system for read-write</span>
<span class="fu">mount</span> -o remount,rw /
<span class="fu">mount</span> -o remount,rw /system
<span class="co"># create a file with the list it items to delete</span>
<span class="fu">cat</span> <span class="op">&gt;</span> /sdcard/ungoogle.sh <span class="op">&lt;&lt; EOF</span>
rm -rf /system/app/CarHomeGoogle*
rm -rf /system/app/ChromeBookmarksSyncAdapter*
rm -rf /system/app/ConfigUpdater*
rm -rf /system/app/FaceLock*
rm -rf /system/app/GenieWidget*
rm -rf /system/app/Gmail*
rm -rf /system/app/Gmail2
rm -rf /system/app/GmsCore*
rm -rf /system/app/Google*
rm -rf /system/app/LatinImeTutorial*
rm -rf /system/app/LatinImeDictionaryPack*
rm -rf /system/app/MarketUpdater*
rm -rf /system/app/MediaUploader*
rm -rf /system/app/NetworkLocation*
rm -rf /system/app/OneTimeInitializer*
rm -rf /system/app/Phonesky*
rm -rf /system/app/PlayStore*
rm -rf /system/app/SetupWizard*
rm -rf /system/app/Talk*
rm -rf /system/app/Talkback*
rm -rf /system/app/Vending*
rm -rf /system/app/VoiceSearch*
rm -rf /system/app/VoiceSearchStub*
rm -rf /system/etc/permissions/com.google.android.maps.xml
rm -rf /system/etc/permissions/com.google.android.media.effects.xml
rm -rf /system/etc/permissions/com.google.widevine.software.drm.xml
rm -rf /system/etc/permissions/features.xml
rm -rf /system/etc/preferred-apps/google.xml
rm -rf /system/etc/g.prop
rm -rf /system/addon.d/70-gapps.sh
rm -rf /system/framework/com.google.android.maps.jar
rm -rf /system/framework/com.google.android.media.effects.jar
rm -rf /system/framework/com.google.widevine.software.drm.jar
rm -rf /system/lib/libfilterpack_facedetect.so
rm -rf /system/lib/libfrsdk.so
rm -rf /system/lib/libgcomm_jni.so
rm -rf /system/lib/libgoogle_recognizer_jni.so
rm -rf /system/lib/libgoogle_recognizer_jni_l.so
rm -rf /system/lib/libfacelock_jni.so
rm -rf /system/lib/libfacelock_jni.so
rm -rf /system/lib/libgtalk_jni.so
rm -rf /system/lib/libgtalk_stabilize.so
rm -rf /system/lib/libjni_latinimegoogle.so
rm -rf /system/lib/libflint_engine_jni_api.so
rm -rf /system/lib/libpatts_engine_jni_api.so
rm -rf /system/lib/libspeexwrapper.so
rm -rf /system/lib/libvideochat_stabilize.so
rm -rf /system/lib/libvoicesearch.so
rm -rf /system/lib/libvorbisencoder.so
rm -rf /system/lib/libpicowrapper.so
rm -rf /system/priv-app/CarHomeGoogle*
rm -rf /system/priv-app/ChromeBookmarksSyncAdapter*
rm -rf /system/priv-app/ConfigUpdater*
rm -rf /system/priv-app/FaceLock*
rm -rf /system/priv-app/GenieWidget*
rm -rf /system/priv-app/Gmail*
rm -rf /system/priv-app/GmsCore*
rm -rf /system/priv-app/Google*
rm -rf /system/priv-app/LatinImeTutorial*
rm -rf /system/priv-app/LatinImeDictionaryPack*
rm -rf /system/priv-app/MarketUpdater*
rm -rf /system/priv-app/MediaUploader*
rm -rf /system/priv-app/NetworkLocation*
rm -rf /system/priv-app/OneTimeInitializer*
rm -rf /system/priv-app/Phonesky*
rm -rf /system/priv-app/PlayStore*
rm -rf /system/priv-app/SetupWizard*
rm -rf /system/priv-app/Talk*
rm -rf /system/priv-app/Talkback*
rm -rf /system/priv-app/Vending*
rm -rf /system/priv-app/VoiceSearch*
rm -rf /system/priv-app/VoiceSearchStub*
EOF
# execut the created list
sh /sdcard/ungoogle.sh</code></pre></div>
<h3 id="install-nanodroid-microg">3. Install NanoDroid microG</h3>
<p>Once Google is cleaned up and the FakeGapps module is ready, reboot into recovery (see 12. and 13.) and install the NanoDroid zip via TWRP.</p>
<p>If you done everything right, there will be no Google Services or apps left, if not - as I did - a few leftovers will need to be manually cleaned up.</p>
<p>If the microG flashing was successful, an app, called <code>microG settings</code> will show up:</p>
<figure class="photo">
<a href="https://petermolnar.net/files/nomu_s10_ungoogle_microg_b.png" class=""> <img src="https://petermolnar.net/files/nomu_s10_ungoogle_microg_z.png" title="" alt="FakeGApps in Xposed" class="adaptimg" width="407" height="640" /> </a>
<figcaption>
FakeGApps in Xposed
</figcaption>
</figure>
<p><strong>Done!</strong></p>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p><a href="http://bradfrost.com/blog/post/google-you-creepy-sonofabitch/" class="uri">http://bradfrost.com/blog/post/google-you-creepy-sonofabitch/</a><a href="#fnref1">↩</a></p></li>
<li id="fn2"><p><a href="https://myaccount.google.com/activitycontrols/location" class="uri">https://myaccount.google.com/activitycontrols/location</a><a href="#fnref2">↩</a></p></li>
<li id="fn3"><p><a href="https://qz.com/1131515/google-collects-android-users-locations-even-when-location-services-are-disabled/" class="uri">https://qz.com/1131515/google-collects-android-users-locations-even-when-location-services-are-disabled/</a><a href="#fnref3">↩</a></p></li>
<li id="fn4"><p><a href="https://www.eugdpr.org/" class="uri">https://www.eugdpr.org/</a><a href="#fnref4">↩</a></p></li>
<li id="fn5"><p><a href="https://en.wikipedia.org/wiki/HTC_Bravo" class="uri">https://en.wikipedia.org/wiki/HTC_Bravo</a><a href="#fnref5">↩</a></p></li>
<li id="fn6"><p><a href="https://microg.org/" class="uri">https://microg.org/</a><a href="#fnref6">↩</a></p></li>
<li id="fn7"><p><a href="http://www.nomu.hk/pro/s10_product_show/" class="uri">http://www.nomu.hk/pro/s10_product_show/</a><a href="#fnref7">↩</a></p></li>
<li id="fn8"><p><a href="https://spflashtool.com/download/SP_Flash_Tool_v5.1744_Linux.zip" class="uri">https://spflashtool.com/download/SP_Flash_Tool_v5.1744_Linux.zip</a><a href="#fnref8">↩</a></p></li>
<li id="fn9"><p><a href="https://www.needrom.com/download/how-to-setup-sp-flash-tool-linux-mtk" class="uri">https://www.needrom.com/download/how-to-setup-sp-flash-tool-linux-mtk</a><a href="#fnref9">↩</a></p></li>
<li id="fn10"><p><a href="https://forum.xda-developers.com/showthread.php?t=3482755" class="uri">https://forum.xda-developers.com/showthread.php?t=3482755</a><a href="#fnref10">↩</a></p></li>
<li id="fn11"><p><a href="https://forum.xda-developers.com/attachment.php?attachmentid=3947063" class="uri">https://forum.xda-developers.com/attachment.php?attachmentid=3947063</a><a href="#fnref11">↩</a></p></li>
<li id="fn12"><p><a href="http://www.supersu.com/" class="uri">http://www.supersu.com/</a><a href="#fnref12">↩</a></p></li>
<li id="fn13"><p><a href="https://s3-us-west-2.amazonaws.com/supersu/download/zip/SuperSU-v2.82-201705271822.zip" class="uri">https://s3-us-west-2.amazonaws.com/supersu/download/zip/SuperSU-v2.82-201705271822.zip</a><a href="#fnref13">↩</a></p></li>
<li id="fn14"><p><a href="https://forum.xda-developers.com/showthread.php?t=3034811" class="uri">https://forum.xda-developers.com/showthread.php?t=3034811</a><a href="#fnref14">↩</a></p></li>
<li id="fn15"><p><a href="https://github.com/microg/android_packages_apps_GmsCore/wiki/Signature-Spoofing" class="uri">https://github.com/microg/android_packages_apps_GmsCore/wiki/Signature-Spoofing</a><a href="#fnref15">↩</a></p></li>
<li id="fn16"><p><a href="https://dl-xda.xposed.info/framework/sdk23/arm/xposed-v89-sdk23-arm.zip" class="uri">https://dl-xda.xposed.info/framework/sdk23/arm/xposed-v89-sdk23-arm.zip</a><a href="#fnref16">↩</a></p></li>
<li id="fn17"><p><a href="https://forum.xda-developers.com/attachment.php?attachmentid=4393082" class="uri">https://forum.xda-developers.com/attachment.php?attachmentid=4393082</a><a href="#fnref17">↩</a></p></li>
<li id="fn18"><p><a href="http://nanolx.org/nanolx/nanodroid" class="uri">http://nanolx.org/nanolx/nanodroid</a><a href="#fnref18">↩</a></p></li>
<li id="fn19"><p><a href="https://downloads.nanolx.org/NanoDroid/Stable/NanoDroid-microG-16.1.20180209.zip" class="uri">https://downloads.nanolx.org/NanoDroid/Stable/NanoDroid-microG-16.1.20180209.zip</a><a href="#fnref19">↩</a></p></li>
<li id="fn20"><p><a href="http://repo.xposed.info/module/com.thermatk.android.xf.fakegapps" class="uri">http://repo.xposed.info/module/com.thermatk.android.xf.fakegapps</a><a href="#fnref20">↩</a></p></li>
</ol>
</section>]]></content>
    <link href="https://petermolnar.net/odexed-android-6-marshmallow-with-microg/" rel="alternate"/>
    <published>2018-02-24T10:00:00+00:00</published>
    <rights>2018 Peter Molnar CC BY 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/hol-vasmu-allott-most-kohalom</id>
    <title>Hol vasmű állott, most kőhalom</title>
    <updated>2018-02-15T10:00:00+00:00</updated>
    <content type="CDATA"><![CDATA[<p>Just another abandoned, decaying factory in Budapest.</p>]]></content>
    <link href="https://petermolnar.net/hol-vasmu-allott-most-kohalom/" rel="alternate"/>
    <published>2018-02-15T10:00:00+00:00</published>
    <rights>2018 Peter Molnar CC BY-NC-ND 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/hol-vasmu-allott-most-kohalom-2</id>
    <title>Hol vasmű állott, most kőhalom 2</title>
    <updated>2018-02-14T10:00:00+00:00</updated>
    <content type="CDATA"><![CDATA[<p>Empty windows, nature slowly taking over, rubble everywhere - a few decades of decay.</p>]]></content>
    <link href="https://petermolnar.net/hol-vasmu-allott-most-kohalom-2/" rel="alternate"/>
    <published>2018-02-14T10:00:00+00:00</published>
    <rights>2018 Peter Molnar CC BY-NC-ND 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/linkedin-public-settings-ignored</id>
    <title>LinkedIn is ignoring user settings</title>
    <updated>2018-01-14T12:00:00+00:00</updated>
    <content type="CDATA"><![CDATA[<p>A few days ago, on the #indieweb Freenode channel<a href="#fn1" class="footnoteRef" id="fnref1"><sup>1</sup></a> one of the users asked if we knew an indieweb-friendly way of getting data out of LinkedIn. I wasn't paying attention to any recent news related to LinkedIn, though I've heard a few things, such as they are struggling to prevent data scraping: the note mentioned that they believe it's a problem that employers keep an eye on changes in LinkedIn profiles via 3rd party. This, indeed, can be an issue, but there are ways to manage this within LinkedIn: your public profile settings<a href="#fn2" class="footnoteRef" id="fnref2"><sup>2</sup></a>.</p>
<p>In my case, this was set to visible to everyone for years, and by the time I had to set it up (again: years), it was working as intended. But a few days ago, for my surprise, visiting my profile while logged out resulted in this:</p>
<figure class="photo">
<a href="https://petermolnar.net/files/linkedin-public-profile-issues-authwall_b.png" class=""> <img src="https://petermolnar.net/files/linkedin-public-profile-issues-authwall_z.png" title="" alt="LinkedIn showing a paywall-like 'authwall' for profiles set explicitly to public for everyone" class="adaptimg" width="640" height="521" /> </a>
<figcaption>
LinkedIn showing a paywall-like 'authwall' for profiles set explicitly to public for everyone
</figcaption>
</figure>
<p>and this:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">wget</span> -O- https://www.linkedin.com/in/petermolnareu
<span class="ex">--2018-01-14</span> 10:26:12--  https://www.linkedin.com/in/petermolnareu
<span class="ex">Resolving</span> www.linkedin.com (www.linkedin.com)<span class="ex">...</span> 91.225.248.129, 2620:109:c00c:104::b93f:9001
<span class="ex">Connecting</span> to www.linkedin.com (www.linkedin.com)<span class="kw">|</span><span class="ex">91.225.248.129</span><span class="kw">|</span>:<span class="ex">443...</span> connected.
<span class="ex">HTTP</span> request sent, awaiting response... 999 Request denied
<span class="ex">2018-01-14</span> 10:26:12 ERROR 999: Request denied.</code></pre></div>
<p>or this:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">curl</span> https://www.linkedin.com/in/petermolnareu
<span class="op">&lt;</span><span class="ex">html</span><span class="op">&gt;&lt;</span>head<span class="op">&gt;</span>
<span class="op">&lt;</span><span class="ex">script</span> type=<span class="st">&quot;text/javascript&quot;</span><span class="op">&gt;</span>
<span class="ex">window.onload</span> = function() <span class="kw">{</span>
  <span class="ex">//</span> Parse the tracking code from cookies.
  <span class="ex">var</span> trk = <span class="st">&quot;bf&quot;</span><span class="kw">;</span>
  <span class="ex">var</span> trkInfo = <span class="st">&quot;bf&quot;</span><span class="kw">;</span>
  <span class="ex">var</span> cookies = document.cookie.split(<span class="st">&quot;; &quot;</span>);
  <span class="kw">for</span> <span class="kw">(</span><span class="ex">var</span> i = 0<span class="kw">;</span> <span class="ex">i</span> <span class="op">&lt;</span> cookies.length<span class="kw">;</span> <span class="ex">++i</span><span class="kw">)</span> <span class="kw">{</span>
    <span class="kw">if</span> <span class="kw">((</span>cookies[i].indexOf(<span class="st">&quot;trkCode=&quot;</span>) == 0) &amp;&amp; (cookies[i].length &gt; 8)) {
      trk = cookies[i].substring(8);
    }
    else if ((cookies[i].indexOf(<span class="st">&quot;trkInfo=&quot;</span>) == 0) &amp;&amp; (cookies[i].length &gt; 8)) {
      trkInfo = cookies[i].substring(8);
    }
  }

  if (window.location.protocol == <span class="st">&quot;http:&quot;</span>) {
    // If <span class="st">&quot;sl&quot;</span> cookie is set, redirect to https.
    for (var i = 0; i &lt; cookies.length; ++i) {
      if ((cookies[i].indexOf(<span class="st">&quot;sl=&quot;</span>) == 0) &amp;&amp; (cookies[i].length &gt; 3)) {
        window.location.href = <span class="st">&quot;https:&quot;</span> + window.location.href.substring(window.location.protocol.length);
        return;
      }
    }
  }

  // Get the new domain. For international domains such as
  // fr.linkedin.com, we convert it to www.linkedin.com
  var domain = <span class="st">&quot;www.linkedin.com&quot;</span>;
  if (domain != location.host) {
    var subdomainIndex = location.host.indexOf(<span class="st">&quot;.linkedin&quot;</span>);
    if (subdomainIndex != -1) {
      domain = <span class="st">&quot;www&quot;</span> + location.host.substring(subdomainIndex);
    }
  }

  window.location.href = <span class="st">&quot;https://&quot;</span> + domain + <span class="st">&quot;/authwall?trk=&quot;</span> + trk + <span class="st">&quot;&amp;trkInfo=&quot;</span> + trkInfo +
      <span class="st">&quot;&amp;originalReferer=&quot;</span> + document.referrer.substr(0, 200) +
      <span class="st">&quot;&amp;sessionRedirect=&quot;</span> + encodeURIComponent(window.location.href);
}
&lt;/script&gt;
&lt;/head&gt;&lt;/html&gt;</code></pre></div>
So I started digging. According to the LinkedIn FAQ<a href="#fn3" class="footnoteRef" id="fnref3"><sup>3</sup></a> there is a page where you can set your profile's public visibility. Those settings, for me, were still set to:
<figure class="photo">
<a href="https://petermolnar.net/files/linkedin-public-profile-issues-settings_b.png" class=""> <img src="https://petermolnar.net/files/linkedin-public-profile-issues-settings_z.png" title="" alt="LinkedIn public profile settings" class="adaptimg" width="199" height="640" /> </a>
<figcaption>
LinkedIn public profile settings
</figcaption>
</figure>
<p>Despite the settings, there is no public profile for logged out users.</p>
<p>I'd like to understand what it going on, because so far, this looks like a fat lie from LinkedIn. Hopefully just a bug.</p>
<h2 id="update">UPDATE</h2>
<p><del>I tried setting referrers and user agents, used different IP addresses, still nothing.</del> I can't type today and managed to mistype <code>https://google.com</code> - the referrer ended up as <code>https:/google.com</code>. So, following the notes on HN, setting a referrer to Google sometimes works. After a few failures it will lock you out again, referrer or not. This is even uglier if it was a proper authwall for everyone.</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">curl</span> <span class="st">&#39;https://www.linkedin.com/in/petermolnareu&#39;</span> \
-e <span class="st">&#39;https://google.com/&#39;</span> \
-H <span class="st">&#39;accept-encoding: text&#39;</span> -H \
<span class="st">&#39;accept-language: en-US,en;q=0.9,&#39;</span> \
-H <span class="st">&#39;user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36&#39;</span></code></pre></div>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="dt">&lt;!DOCTYPE </span>html<span class="dt">&gt;</span>...</code></pre></div>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p><a href="https://chat.indieweb.org" class="uri">https://chat.indieweb.org</a><a href="#fnref1">↩</a></p></li>
<li id="fn2"><p><a href="https://www.linkedin.com/public-profile/settings" class="uri">https://www.linkedin.com/public-profile/settings</a><a href="#fnref2">↩</a></p></li>
<li id="fn3"><p><a href="https://www.linkedin.com/help/linkedin/answer/83?query=public" class="uri">https://www.linkedin.com/help/linkedin/answer/83?query=public</a><a href="#fnref3">↩</a></p></li>
</ol>
</section>]]></content>
    <link href="https://petermolnar.net/linkedin-public-settings-ignored/" rel="alternate"/>
    <published>2018-01-14T12:00:00+00:00</published>
    <rights>2018 Peter Molnar CC BY 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/nomu-s10</id>
    <title>Living with a rugged, cheap Chinese Android phone, the Nomu S10</title>
    <updated>2018-01-07T11:00:00+00:00</updated>
    <content type="CDATA"><![CDATA[<p><em>Disclaimer: I didn't get paid for this entry: I simply liked this phone enough to share the whys and the fixes for it's problems. It also serves as notes for myself.</em></p>
<h2 id="how-the-nomu-s10-found-me">How the Nomu S10 found me</h2>
<figure class="photo">
<a href="https://petermolnar.net/files/nomu_s10_front_b.jpg" class=""> <img src="https://petermolnar.net/files/nomu_s10_front_z.jpg" title="" alt="Nomu S10 after a few months of regular use in pockets and bags" class="adaptimg" width="411" height="640" /> </a>
<figcaption>
Nomu S10 after a few months of regular use in pockets and bags<span class="author"> - photo by Peter Molnar</span>
<dl class="exif">
<dt>
Camera
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-camera" />
</svg>
PENTAX K-5 II s
</dd>
<dt>
Aperture
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-aperture" />
</svg>
f/8.0
</dd>
<dt>
Shutter speed
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-clock" />
</svg>
1/40 sec
</dd>
<dt>
Focal length (as set)
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-focallength" />
</svg>
85.0 mm
</dd>
<dt>
Sensitivity
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-sensitivity" />
</svg>
ISO 3200
</dd>
<dt>
Lens
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-lens" />
</svg>
HD PENTAX-DA 16-85mm F3.5-5.6 ED DC WR
</dd>
</dl>
</figcaption>
</figure>
<p>About half a year ago we (my wife and me) decided we need to replace our phones - again. The Galaxy S4 was a sad choice after a Nexus 4 and a Galaxy Nexus, but those had to be replaced: they were literally falling apart and none of them supported European LTE bands - 3, 8, 20.</p>
<p>The Samsung Galaxy S4 is a weak, fragile creature, with weird bugs, a bloated, locked system, and with a slippery, rounded-everywhere shape that will certainly land on the tarmac a few times due to this. It has an impressive range of sensors, but that's it, and I wanted a phone which works reliably and which is a bit more rugged.</p>
<p>I don't believe in the current trend of bezel-free design and I'm very tired of all-glass, expensive, overheating, bloated, gargantuan &quot;phones&quot;, so I started looking into less-known brands. Initially into names like Oneplus, Xiaomi and some similar, sort of established manufacturers, but soon I learnt getting the actually good models in Europe is quite hard and due to this, nearly none of them covers LTE band 20 - the one giffgaff<a href="#fn1" class="footnoteRef" id="fnref1"><sup>1</sup></a>, my service provider has access to.</p>
<p>The danger of less known brands is that they can be cheap for very logical reason: many of them are built cheap. On the bright side, the usually bring a surprising but good feature: most of the time these phones feature a near-raw AOSP system, without any bloat, and only minor software modifications.</p>
<p>Not long into my search I found a gem: the Nomu S10<a href="#fn2" class="footnoteRef" id="fnref2"><sup>2</sup></a> and the Nomu brand in general. Apart from the S10, there is now the S10 Pro<a href="#fn3" class="footnoteRef" id="fnref3"><sup>3</sup></a>, the S20<a href="#fn4" class="footnoteRef" id="fnref4"><sup>4</sup></a> and S30<a href="#fn5" class="footnoteRef" id="fnref5"><sup>5</sup></a> model. All of them are IP68 - waterproof enough to survive being fully submerged under water for a little while, rugged, with huge batteries.</p>
<p>The S10 is a relatively basic phone. It lacks NFC, 5GHz WiFi, and any new, shiny tech - but it's loud, has a very capable 5000mAh battery, and covers more or less anything that falls under regular use. <em>Yes, it's heavy.</em> It also has a metal frame inside - some way it resembles a ThinkPad from the old days. It packs 2GB RAM, 16GB storage, and either expandable with microSD, or it accepts 2 SIMs. The screen is a HD IPS screen, which I prefer to the OLED screens personally. The 2.4GHz Wifi is fast and stable and has a better reception than my previous, high-end phones did.</p>
<p>It also comes with a protective foil by default - actually it comes with 2 layers; after removing the first, a second is still protecting the display. Also has some version of Qualcomm Quick Charge which is also supported by the European charger it came with, which is certainly needed for the monster battery it has.</p>
<p>Though the stock firmware accepted SD cards up to 32GB, for my surprise, one of the system updates made 64GB possible as well - I don't have any bigger ones, but I wouldn't be surprised if the upper limit was 256GB.</p>
<p>Overall it has all the features you'd want from a phone that was built to last and be a companion for rough weather or outdoor activities.</p>
<p>I got a very good phone for the price and it worked well enough that I even used it without rooting.</p>
<figure class="photo">
<a href="https://petermolnar.net/files/nomu_s10_back_b.jpg" class=""> <img src="https://petermolnar.net/files/nomu_s10_back_z.jpg" title="" alt="Same Nomu S10, again after a few months of use" class="adaptimg" width="411" height="640" /> </a>
<figcaption>
Same Nomu S10, again after a few months of use<span class="author"> - photo by Peter Molnar</span>
<dl class="exif">
<dt>
Camera
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-camera" />
</svg>
PENTAX K-5 II s
</dd>
<dt>
Aperture
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-aperture" />
</svg>
f/8.0
</dd>
<dt>
Shutter speed
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-clock" />
</svg>
1/40 sec
</dd>
<dt>
Focal length (as set)
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-focallength" />
</svg>
85.0 mm
</dd>
<dt>
Sensitivity
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-sensitivity" />
</svg>
ISO 3200
</dd>
<dt>
Lens
</dt>
<dd>
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-lens" />
</svg>
HD PENTAX-DA 16-85mm F3.5-5.6 ED DC WR
</dd>
</dl>
</figcaption>
</figure>
<h2 id="troubles-arise-the-triada-malware-fiasco">Troubles arise: the Triada malware fiasco</h2>
<p>One day my wife came home, telling me that her phone started to act weird: it randomly shows full screen popups, installs random software, and generally acting weird.</p>
<p>Knowing the she is quite careful and not installing basically anything on her phone after a few initial, well established apps, it looked very much like either a hack through other networks she connects to or something completely out of our control.</p>
<p>Unfortunately it turned out to be the later: there has been reports that a good amount of similarly cheap Chinese phones <strong>got infected with malware - from the manufacturer</strong><a href="#fn6" class="footnoteRef" id="fnref6"><sup>6</sup></a>! Interestingly the malware did not manifest on my phone, but the possibility that it might got me itchy.</p>
<p>Having a little experience in flashing custom ROMs the solution was obvious: I need to find a malware-free ROM for the Nomu and re-flash them.</p>
<p>The last time I had to dwell deep into Android and flashing was with my beloved HTC Desire - in 2011 - apparently a lot has changed since, and the malware, Triada, managed to sit into a part of the system, called Zygote, which is deep enough that even with root privileges you couldn't get rid of it.</p>
<p><strong>So here is how to get rid of the Triada malware on a Nomu S10 by flashing a malware free ROM.</strong></p>
<h3 id="the-search-for-a-malware-free-rom---the-downside-of-niche-phones">The search for a malware-free ROM - the downside of niche phones</h3>
<p>When you have a widely bought and known phone there are usually plenty of custom ROMs floating around for it. This used to be the case for the Nexus 4, the Galaxy S4, even for the HTC Desire.</p>
<p>This is not the case with the Nomu S10: it's rare, so nobody made a full-on custom ROM for it. I started looking into forums and threads on the topic and thankfully I came across a French forum<a href="#fn7" class="footnoteRef" id="fnref7"><sup>7</sup></a> mentioning something called Archos 50 Saphir ROM. I was hoping to find some <del>Cyanogenmod</del> LineageOS based ROM, but for my surprise, Archos is a retailer: they sell Archos branded phones. One of these is what they call 50 Saphir<a href="#fn8" class="footnoteRef" id="fnref8"><sup>8</sup></a> - which is a re-branded Nomu S10.</p>
<p>Apparently the S10 had been sold to various &quot;brands&quot; who buy cheap phones, put a logo and a ROM without malware on them, and sell them for double the price.</p>
<p>This is the Archos ROM I ended up using:</p>
<p><a href="https://www.dropbox.com/s/yk2t2mlv1uch0y7/Archos-50-Saphir-14-OTA.zip?dl=0" class="uri">https://www.dropbox.com/s/yk2t2mlv1uch0y7/Archos-50-Saphir-14-OTA.zip?dl=0</a></p>
<p><em>You can also get it from needrom.com<a href="#fn9" class="footnoteRef" id="fnref9"><sup>9</sup></a>.</em> It's 1.1GB, so if you're tight on bandwidth, don't grab it immediately.</p>
<p>Another option is to get one of the newer, official Nomu ROMs, from their own site<a href="#fn10" class="footnoteRef" id="fnref10"><sup>10</sup></a> - the version 1.1.4, uploaded 2017-11-11 seems to be running fine and malware free for me for the past month:</p>
<p><a href="https://drive.google.com/file/d/1ZXlnlic2z10CSFGoCf7nHygfTWsnFsAq/" class="uri">https://drive.google.com/file/d/1ZXlnlic2z10CSFGoCf7nHygfTWsnFsAq/</a></p>
<p>I went with this solution, although it's reasonable to be cautious and stick to the Archos version.</p>
<h3 id="oem-unlocking---the-upside-of-oem-phones">OEM Unlocking - the upside of OEM phones</h3>
<p><strong>Note: OEM unlocking can trigger a complete factory reset, deleting anything on your phone, excluding the microSD card. I'd still suggest removing even the microSD card and saving everything before starting the process.</strong></p>
<p>Since the S10 has been sold for other brands to be used with their labels, Nomu allowed these resellers to flash their own operating system. This is good news for us: it means we can do the same, and rather simply.</p>
<p>There is a process in newer Androids that protects your phone from being overwritten by a mere USB cable and a laptop which requires a working operating system. If you don't have one, you won't be able to unlock flashing on your phone, so first to a factory reset and then follow these steps.</p>
<p>In your running Android:</p>
<ol type="1">
<li>enable the <code>Developer mode</code>:</li>
<li>go to <code>Settings</code></li>
<li>enter <code>About phone</code></li>
<li>tap <code>Build number</code> approximately 6 times</li>
<li>once you have <code>Developer options</code> under <code>Settings</code>, enter it</li>
<li>enable <code>OEM unlocking</code></li>
</ol>
<p>For the next stage, you'll need a tool called <code>fastboot</code>. I'm using Debian, which is a linux distribution, and I have <code>fastboot</code> available from <code>apt</code> <em>(the Debian app store, one could say)</em> . I have no idea how to install it on Windows, but there are many tutorial available on the internet, and the commands should work once you have it. The commands below need to be executed as <code>root</code> user in linux.</p>
<p>Once you have fastboot:</p>
<ol type="1">
<li>power off your phone</li>
<li>press and hold <code>volume up</code> and the <code>power</code> buttons</li>
<li>you'll be greeted with a little menu, looking like:</li>
</ol>
<pre><code>Select Boot Mode:
[VOLUME_UP to select, VOLUME_DOW is OK.]

[Recovery   Mode]       &lt;&lt;==
[Fastboot   Mode]
[Normal     Mode]</code></pre>
<p>Select <code>Fastboot mode</code> by pressing <code>volume up</code> and press <code>volume down</code>. The screen will now print a new line:</p>
<pre><code>=&gt; FASTBOOT mode...</code></pre>
<p>On your laptop, connect the USB cable and test if you see the device in your laptop:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">fastboot</span> devices
<span class="ex">EEMRTK5PBEJ78DWS</span>        fastboot</code></pre></div>
<p><strong>If you don't see the device, there is something wrong; try to repeat the process from the beginning of OEM unlocking.</strong></p>
<p>Now enter:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">fastboot</span> flashing unlock</code></pre></div>
<p><strong>Note: it used to be</strong> <code>fastboot oem unlock</code>. Now it's <code>fastboot flashing unlock</code>. If it doesn't work, try the oem command version.</p>
<p>It will ask for confirmation and you'll have to press the corresponding volume button - read the instructions on the phone, but <code>volume up</code> should enable to unlock.</p>
<p>Congratulations! You can now install any ROM made for your phone.</p>
<p><strong>Things that might go wrong with this:</strong></p>
<ul>
<li>to get out of fastboot press and hold the <code>power</code> button for a while</li>
<li>if neither <code>fastboot oem unlock</code> nor <code>fastboot flashing unlock</code> does nothing, do a factory reset, a cache clear, and re-enable OEM unlocking. For this, you need to boot the <code>recovery</code> option instead of fastboot, the select the options.</li>
<li>if this didn't work either... well, that's a problem, and I don't have a simple, fast, or working solution.</li>
</ul>
<h3 id="flashing-the-stock-rom">Flashing the stock ROM</h3>
<p>Originally I tried to flash the stock ROM via the stock recovery, but every single time I got an error, telling me the zip file is corrupted. After a while I decided to take another approach.</p>
<p>Once you have the stock ZIP, extract it:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">unzip</span> NOMU_S10_COMMON_V1.1.4_2017_11_01_FQ5C62WTE1D.XWP.NOMU.M0.HB.H.SSXSSYDAKLS23.1101.V3.14.zip -d nomu_s10_1.1.4</code></pre></div>
<p>It will create the following files in the <code>nomu_s10_1.1.4</code> directory:</p>
<pre><code>APDB_MT6735_S01_alps-mp-m0.mp1_W16.47
boot.img
boot-verified.img
BPLGUInfoCustomAppSrcP_MT6735_S00_MOLY_LR9_W1444_MD_LWTG_MP_V88_P92_1_lwg_n
cache.img
Checksum.ini
lk.bin
lk-verified.bin
logo.bin
logo-verified.bin
md1arm7.img
md1dsp.img
md1rom.img
md3rom.img
MT6737T_Android_scatter.txt
preloader_fq5c62wt_xwp_nomu.bin
preloader.img
recovery.img
recovery-verified.img
secro.img
secro-verified.img
system.img
trustzone.bin
userdata.img
V18S NOMU 软件配置说明.txt</code></pre>
<p><strong>The following will render your phone temporarily useless,; you will be without recovery, left only with a bootloader, for a short period. I seriously recommend only doing this with a fully charged phone and avoiding any accidental reboots during the process.</strong></p>
<p>First, wipe the relevant partitions:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">fastboot</span> erase system
<span class="ex">fastboot</span> erase boot
<span class="ex">fastboot</span> erase recovery
<span class="ex">fastboot</span> erase cache</code></pre></div>
<p>Now flash them:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="bu">cd</span> nomu_s10_1.1.4
<span class="ex">fastboot</span> flash boot boot.img
<span class="ex">fastboot</span> flash recovery recovery.img
<span class="ex">fastboot</span> flash cache cache.img
<span class="ex">fastboot</span> flash system system.img</code></pre></div>
<p>It will take a while, be patient.</p>
<h3 id="flashing-the-archos-rom">Flashing the Archos ROM</h3>
<p>Just use the same method as above, but instead of extracting the Nomu zip, extract the Archos zip.</p>
<p>However, unlike the stock ROM, the Archos ROM can be installed via <code>adb sideload</code> or simply selecting the zip in the <code>dirty booted TWRP recovery</code> described below. In case you're familiar with custom recovery zip installing then there's no need for the extract magic, but that method works just fine as well.</p>
<h3 id="optional-how-to-root-the-nomu-s10---dirty-booting-twrp-recovery">[Optional] How to root the Nomu S10 - dirty booting TWRP recovery</h3>
<p>Dirty boot means we don't flash the recovery partition, only load it on the fly from the laptop and use it temporarily - think about it as a live linux distribution. I found a TWRP custom recovery which worked very well for me; unfortunately I don't remember the source, so I've uploaded it to Dropbox:</p>
<p>TWRP 3.0.2 recovery for Nomu S10:</p>
<p><a href="https://www.dropbox.com/s/yebkei44w9oq6kh/nomu-s10_twrp-3.0.2.img?dl=0" class="uri">https://www.dropbox.com/s/yebkei44w9oq6kh/nomu-s10_twrp-3.0.2.img?dl=0</a></p>
<p>Once you're in the recovery, you can install whatever you want, including SuperSu:</p>
<p><a href="https://www.dropbox.com/s/semoj6evcio3nyw/SuperSU-v2.82-201705271822.zip?dl=0" class="uri">https://www.dropbox.com/s/semoj6evcio3nyw/SuperSU-v2.82-201705271822.zip?dl=0</a></p>
<p>SuperSu is a &quot;systemless root&quot; method, which means instead of touching the <code>system</code> partition, which triggers an alarm with many root detectors, it puts the <code>su</code> binary into <code>/sbin</code> - which is part of the boot image. It can be simply reverted, should that be needed.</p>
<p>I didn't have luck installing Magisk. While it worked flawlessly on a LineageOS install on a Nexus 10, no matter what I tried, it never worked here, but every single attempt triggered a factory reset. Just use SuperSu. If you do need features Magisk provides, maybe take a look at the Xposed framework instead, that worked well:</p>
<p><a href="https://www.dropbox.com/s/xuqmah3vwh3nsre/xposed-v88.2-sdk23-arm.zip?dl=0" class="uri">https://www.dropbox.com/s/xuqmah3vwh3nsre/xposed-v88.2-sdk23-arm.zip?dl=0</a></p>
<p>The simplest for these to install is to put them on the microSD card and select them as zip to be installed from TWRP. I will not cover the process this here, there are very good howto about TWRP and installing zips.</p>
<h2 id="waterproof---as-long-as-you-are-careful">Waterproof - as long as you are careful</h2>
<p>Not too long ago we spent a few days abroad, next to the sea, and found a tiny pool, left with water during low tide. The pool had a surprising amount if small fish and a small amount of coral in it, and, since the S10 is marketed as IP68, we decided to take a few underwater pictures with it. It's important to say that one of the first things we did when we got the phones was submerge it in a lake to test the waterproofness, and there were not issues, but it was just a few seconds.</p>
<p>It's a terrible underwater camera, so don't use it as one. The touchscreen gets mad and recognizes the water as constant touch, so pushing the camera shutter button is hard and tricky.</p>
<p>But after finishing taking the shoots it seemed like my wife's phone got a leak somewhere. It could have been a tiny bit of looseness in the rubber USB cover, or something completely unrelated, but after a few minutes out of the water it developed weird errors. The screen showed a lot of vertical lines, fading or altering the colour &quot;behind&quot; them, the resolution looked like it fell back to 320x240, and charging worked only sporadically. Apart from this, it was still working, receiving calls, responsive to touch.</p>
<p>After getting home I returned it to Amazon, who, due to the lack of replacement units with the original seller, refunded the value (the phone was less, the 6 months old). In my opinion, and according to the marketing, we did nothing out of ordinary use. We bought another one.</p>
<h2 id="touchscreen-quality-issues-it-may-have-been-the-only-problematic-one-we-got">Touchscreen quality issues? (it may have been the only problematic one we got)</h2>
<p>After buying the replacement for the defective one the next we received seemed fine - until you poked the touchscreen around the 'a' character on the keyboard. From that point, it started acting like the screen was being touched at multiple locations - would have made a nice stock video for ghost movies.</p>
<p>This was an obvious, immediate return, and we are now with yet another one without glitches - I hope it stays like that.</p>
<h2 id="and-youre-still-recommending-it">And you're still recommending it?!</h2>
<p>Oh, yes.</p>
<p>It's cheap, with very nice features: small, compared to the battery it packs; runs for days on a single charge even with reasonable use; waterproof enough to survive being out in the rain for a long while, even submerged (when the flaps a strictly closed); the features it has all work; it's &quot;hackable&quot; by default (the good way, unlike the Samsung or HTC phones).</p>
<p>The malware-by-manufacturer is not a unique problem and not strictly a problem of no-name manufacturers. There were reports that similar issues hit well known brands, such as Samsung, LG, Asus - at a point, the problem even included some Nexus 5 phones by Google<a href="#fn11" class="footnoteRef" id="fnref11"><sup>11</sup></a>.</p>
<p>My sole remaining moan with the phone is that it's glued. To take it apart, you'd need a special machine or a rather precise heat-gun and a steady hand. However, all the ones with screws, like the Blackview BV6000, are much larger and heavier.</p>
<p>So yes: regardless of the potential malware issue, I'm still recommending this phone, but do make sure you have a virus-free OS once before you start using it as your main phone.</p>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p><a href="https://www.giffgaff.com/orders/affiliate/petermolnar2" class="uri">https://www.giffgaff.com/orders/affiliate/petermolnar2</a><a href="#fnref1">↩</a></p></li>
<li id="fn2"><p><a href="http://amzn.to/2AioYRp" class="uri">http://amzn.to/2AioYRp</a><a href="#fnref2">↩</a></p></li>
<li id="fn3"><p><a href="http://amzn.to/2CCxb9e" class="uri">http://amzn.to/2CCxb9e</a><a href="#fnref3">↩</a></p></li>
<li id="fn4"><p><a href="http://amzn.to/2CNBf3a" class="uri">http://amzn.to/2CNBf3a</a><a href="#fnref4">↩</a></p></li>
<li id="fn5"><p><a href="http://amzn.to/2E3E7IK" class="uri">http://amzn.to/2E3E7IK</a><a href="#fnref5">↩</a></p></li>
<li id="fn6"><p><a href="https://news.drweb.com/news/?i=11390&amp;lng=en" class="uri">https://news.drweb.com/news/?i=11390&amp;lng=en</a><a href="#fnref6">↩</a></p></li>
<li id="fn7"><p><a href="https://forums.malwarebytes.com/topic/200072-trojantriada" class="uri">https://forums.malwarebytes.com/topic/200072-trojantriada</a><a href="#fnref7">↩</a></p></li>
<li id="fn8"><p><a href="http://www.archos.com/gb-en/products/smartphones/saphir/archos_50saphir/index.html" class="uri">http://www.archos.com/gb-en/products/smartphones/saphir/archos_50saphir/index.html</a><a href="#fnref8">↩</a></p></li>
<li id="fn9"><p><a href="https://www.getdroidtips.com/stock-rom-archos-50-saphir/" class="uri">https://www.getdroidtips.com/stock-rom-archos-50-saphir/</a><a href="#fnref9">↩</a></p></li>
<li id="fn10"><p><a href="http://www.nomu.hk/s10-rom-download/" class="uri">http://www.nomu.hk/s10-rom-download/</a><a href="#fnref10">↩</a></p></li>
<li id="fn11"><p><a href="https://arstechnica.com/information-technology/2017/03/preinstalled-malware-targets-android-users-of-two-companies/" class="uri">https://arstechnica.com/information-technology/2017/03/preinstalled-malware-targets-android-users-of-two-companies/</a><a href="#fnref11">↩</a></p></li>
</ol>
</section>]]></content>
    <link href="https://petermolnar.net/nomu-s10/" rel="alternate"/>
    <published>2018-01-07T11:00:00+00:00</published>
    <rights>2018 Peter Molnar CC BY 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/puertito-de-los-molinos</id>
    <title>Puertito de los Molinos</title>
    <updated>2017-12-27T08:00:00+00:00</updated>
    <content type="CDATA"><![CDATA[<p>Puertito de los Molinos has huge, steep, beautiful walls of rock surrounding it; unfortunately it's more or less impossible to climb down on them. We planned to get here by sunset, but it was pitch black when we actually arrived, so instead before getting our flight home, we got up early and came back for sunrise. It was worth it: when sunrise comes with the high tide the waves are magnificent, even with a calm weather.</p>]]></content>
    <link href="https://petermolnar.net/puertito-de-los-molinos/" rel="alternate"/>
    <published>2017-12-27T08:00:00+00:00</published>
    <rights>2017 Peter Molnar CC BY-NC-ND 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/road-to-malpais-grande</id>
    <title>Road to Malpaís Grande</title>
    <updated>2017-12-26T08:00:00+00:00</updated>
    <content type="CDATA"><![CDATA[<p>The road to and from Pozo Negro is quiet, but that doesn't mean there is nothing to look at: it comes all the way along the black fields of lava which started at Malpaís Grande and ended up at the sea at Pozo Negro.</p>]]></content>
    <link href="https://petermolnar.net/road-to-malpais-grande/" rel="alternate"/>
    <published>2017-12-26T08:00:00+00:00</published>
    <rights>2017 Peter Molnar CC BY-NC-ND 4.0</rights>
  </entry>
  <entry>
    <id>https://petermolnar.net/pozo-negro</id>
    <title>Pozo Negro</title>
    <updated>2017-12-25T08:00:00+00:00</updated>
    <content type="CDATA"><![CDATA[<p>Pozo Negro got about 2 sentences in the guide book for Fuerteventura, and most of those 2 lines was that it has 2 pescados, fish restaurants. Well, it did, in fact had nice fish, and if you're there by sunset, like we were, it also offers beautiful colours.</p>]]></content>
    <link href="https://petermolnar.net/pozo-negro/" rel="alternate"/>
    <published>2017-12-25T08:00:00+00:00</published>
    <rights>2017 Peter Molnar CC BY-NC-ND 4.0</rights>
  </entry>
</feed>
