Agg. Posts/Blogging-Rapido-con-Standard-Notes.md, ListedDownsync.js

This commit is contained in:
octospacc 2022-12-14 18:29:28 +01:00
parent 78c7abe658
commit 84638a9ed6
3 changed files with 177 additions and 70 deletions

View File

@ -1,78 +1,76 @@
// % Categories = Blog Tecnologia
// % Title = ⚡️ Il blogging rapido con Standard Notes
// % CreatedOn = 2022-12-13
// % HTMLTitle = <span class="twa twa-⚡️"><span>⚡️</span></span> Il blogging rapido con Standard Notes
// % UpdatedOn = 2022-12-14
// % Categories = Blog Tecnologia
# ⚡️ Il blogging rapido con Standard Notes
<p>È da direi parecchio tempo che uso <a href="https://standardnotes.com" rel="noopener nofollow" target="_blank">Standard Notes</a> come app di note personali.<br><br>
<p>È da direi parecchio tempo che uso <a href="https://standardnotes.com" rel="noopener nofollow" target="_blank">Standard Notes</a> come app di note personali.<br>
Anni fa l'avevo scelta per il suo essere libera e open-source, ma allo stesso tempo molto pulita, curata, e funzionale. Capitava a pennello in un momento in cui cercavo qualcosa che supportasse una cifratura dei dati che funziona, senza inficiare sull'usabilità.</p>
<p>Senza dilungarmi su perché poi mi è piaciuto continuare ad usarla, poi ho smesso, e poi ho ripreso fino ad arrivare ad ora, bisogna approfondire su una in particolare delle sue funzioni integrate: <a href="https://listed.to">Listed</a>.<br><br>
<p>Senza dilungarmi su perché poi mi è piaciuto continuare ad usarla, poi ho smesso, e poi ho ripreso fino ad arrivare ad ora, bisogna approfondire su una in particolare delle sue funzioni integrate: <a href="https://listed.to">Listed</a>.<br>
Si tratta di una piattaforma di blogging concepita dagli stessi sviluppatori di Standard Notes, e disponibile per il self-hosting oppure l'uso gratuito sull'istanza ufficiale. Per quanto io non l'abbia mai davvero usata prima, ho trovato fin dall'inizio interessante il concetto e la sua applicazione.</p>
<p>Da qualche giorno fa, invece, ho iniziato a giochicchiare con <a href="https://listed.to/@ChatGPT_Experiences">ChatGPT</a>. Questione totalmente diversa questa, si, ma centra perché mi sono posta una domanda: tutte queste conversazioni che sto facendo con l'intelligenza artificiale, dove posso metterle per tenerle integrali, organizzate, ritrovabili e accessibili a chiunque, e per me facili da caricare?</p>
<p>La scelta stava per ricadere sull'usare <a href="https://gitlab.com/octtspacc/staticoso" rel="noopener nofollow" target="_blank">staticoso</a>, il mio generatore di siti statici che già uso per il sitoctt, ma poi ci ho ripensato.</p>
<p>La scelta stava per ricadere sull'usare <a href="https://gitlab.com/octtspacc/staticoso" rel="noopener nofollow" target="_blank">staticoso</a>, il mio generatore di siti statici che già uso per il <a href="https://sitoctt.octt.eu.org" rel="noopener nofollow" target="_blank">sitoctt</a>, ma poi ci ho ripensato.</p>
<p>Ho pensato che voglio rendere effettivamente la mia collezione di esperienze ritrovabile sul Web, il problema qui però, che ho potuto amaramente constatare in tutti questi mesi, è come il sitoctt ad esempio sia, come dire... I motori di ricerca non se lo cagano. 😭<br><br>
<p>Ho pensato che voglio rendere effettivamente la mia collezione di esperienze ritrovabile sul Web, il problema qui però, che ho potuto amaramente constatare in tutti questi mesi, è come il mio sito ad esempio sia, come dire... I motori di ricerca non se lo cagano. 😭<br>
Oltre ad essere pure questo un argomento a parte, va precisato che la colpa della orrenda sorte del mio sito non dipende dal suo codice; è colpa del dominio e/o dell'host. Quindi, no, non abbandonerò mai il mio staticoso: il solo cambiare generatore (e i template di conseguenza) con uno magari fatto seriamente non risolverebbe alcun problema.</p>
<p>Ho dovuto insomma cestinare totalmente l'idea dell'hosting su GitHub e GitLab, e pensare ad altro.</p>
<ul>
<li><strong>Blogger</strong> di Google? L'esperienza editoriale lascia a desiderare su mobile.</li>
<li><strong>Wordpress</strong>? Sarebbe OK, ma sappiamo cosa potrebbe succedere tra tanti anni a dati inseriti in un sistema complesso; e Wordpress è molto complesso. Ho a fatica trovato un <a href="https://github.com/lonekorean/wordpress-export-to-markdown" rel="noopener nofollow" target="_blank">programmino che converte un suo backup</a> XML in file Markdown... che con buona probabilità si romperà tra qualche anno, essendo non ufficiale, dato che a quanto pare il team di Wordpress ha il vizio di cambiare la struttura di quell'XML di tanto in tanto; altre soluzioni di conversione che avevo trovato un attimo prima erano vecchie di qualche annetto e non hanno funzionato, per dire.</li>
<li>Magari <strong>WriteFreely</strong>? Ho già un account sull'<a href="https://noblogo.org" rel="noopener nofollow" target="_blank">istanza di Devol</a>, ma il limite di blog per ogni account è 5 e non volevo potenzialmente sprecarne uno.</li>
<li><strong>Plume</strong>, forse? Sembrava carina quest'altra piattaforma di blogging ospitata da molti e compatibile con ActivityPub (non una necessità per me, ma comunque un bel bonus), ma non permette di impostare del CSS personalizzato; mancanza grave per questo mio caso d'uso, dato che non c'è altra via per impaginare tutto nella maniera specifica di cui ho bisogno per rappresentare una chat senza boilerplate ad ogni paragrafo di HTML.</li>
<li><strong>WordPress</strong>? Sarebbe OK, ma sappiamo cosa potrebbe succedere tra tanti anni a dati inseriti in un sistema complesso; e WordPress è molto complesso. Ho a fatica trovato un <a href="https://github.com/lonekorean/wordpress-export-to-markdown" rel="noopener nofollow" target="_blank">programmino che converte un suo backup</a> XML in file Markdown... che con buona probabilità si romperà tra qualche anno, essendo non ufficiale, dato che a quanto pare il team di WordPress ha il vizio di cambiare la struttura di quell'XML di tanto in tanto; altre soluzioni di conversione che avevo trovato un attimo prima erano vecchie di qualche annetto e non hanno funzionato, per dire.</li>
<li>Magari <strong><a href="https://writefreely.org" rel="noopener nofollow" target="_blank">WriteFreely</a></strong>? Ho già un account sull'<a href="https://noblogo.org" rel="noopener nofollow" target="_blank">istanza di Devol</a>, ma il limite di blog per ogni account è 5 e non volevo potenzialmente sprecarne uno.</li>
<li><strong><a href="https://joinplu.me" rel="noopener nofollow" target="_blank">Plume</a></strong>, forse? Sembrava carina quest'altra piattaforma di blogging, ospitata da molti e compatibile con ActivityPub (non una necessità per me, ma comunque un bel bonus), ma non permette di impostare del CSS personalizzato; mancanza grave per questo mio caso d'uso, dato che non c'è altra via per impaginare tutto nella maniera specifica di cui ho bisogno per rappresentare una chat senza boilerplate ad ogni paragrafo di HTML.</li>
</ul>
<p>Le mie idee erano finite e, dato che avevo fretta di mettere su questo sito ed iniziare a caricarci le esperienze avute con ChatGPT, considerando quanto <em>mamma mia</em> era comodo copiare ed incollare le cose direttamente in Standard Notes, questo era il momento buono di provare Listed.</p>
<p>A dirla tutta, mi preoccupa un po' il prospetto che il file di backup settimanale delle note possa diventare grosso decine di MB, ma ormai il dado è tratto.<br><br>
<p>A dirla tutta, mi preoccupa un po' il prospetto che il file di backup settimanale delle note possa diventare grosso decine di MB, ma ormai il dado è tratto.<br>
Potrei creare un secondo account da usare solo per le note di ChatGPT, esportandole dal primario ed importandole lì, per poi cancellarle dal primo account per alleggerire la raccolta, però ci sono dei problemi. Forse riesco a trasferire l'username, ma i link ai singoli post saranno rotti, perché includono degli ID automatici, e quindi via di redirect alla home per chi ne segue uno vecchio. Inoltre, i vecchi messaggi del guestbook non vengono ricopiati, e mi sa che nemmeno le iscrizioni via email sono trasferite.</p>
<p>Tutto sommato comunque, per questo scopo limitato ho apprezzato quanto sia efficiente avere Standard Notes come parte finale del mio flusso di lavoro. Mi sembra riduca di molto l'effetto "mi scoccio".</p>
<p>Tutto sommato comunque, per questo scopo limitato ho apprezzato quanto sia efficiente avere Standard Notes come parte finale del mio flusso di lavoro. Mi sembra riduca di molto l'<em>effetto "mi scoccio"</em>.</p>
<p>Considerato dunque il piacere della scoperta, ho per un attimo riflettuto su come il fattore "che palle" sia una delle cose che mi trattiene dall'aggiornare il sitoctt con migliore frequenza.</p>
<p>Considerato dunque il piacere della scoperta, ho per un attimo riflettuto su come il <em>fattore "che palle"</em> sia una delle cose che mi trattiene dall'aggiornare il sitoctt con migliore frequenza.</p>
<p>Lasciamo stare le pagine tematiche, che sono bestie a parte; lasciamo stare pure i post lunghi, che mi richiedono forte ispirazione e abbastanza tempo di stesura; però che cavolo, almeno qualcosa per il MicroBlog mi andrebbe di crearla più spesso! Però mi passa proprio la voglia, per qualche motivo, considerando tutta la trafila che devo fare.<br><br>
<p>Lasciamo stare le pagine tematiche, che sono bestie a parte; lasciamo stare pure i post lunghi, che mi richiedono forte ispirazione e abbastanza tempo di stesura; però che cavolo, almeno qualcosa per il MicroBlog, che va per le corte, mi andrebbe di crearla più spesso! Però mi passa proprio la voglia, per qualche motivo, considerando tutta la trafila che devo fare.<br>
A dire il vero, avevo preso già da subito l'abitudine di iniziare a scrivere un post in Standard Notes, così da poter più flessibilmente gestire il tutto, magari passando rapidamente da un dispositivo all'altro. Però, se dovevo mettermi a scrivere qualcosa di non lungo ed elaborato, da pubblicare prima possibile, non mi veniva proprio la voglia di agire.</p>
<p>"<em>Ma perché non passo a Listed almeno per il mio MicroBlog?</em>", penso dunque. Beh, c'è un motivo se addirittura sono arrivata a farmi il mio generatore di siti statici: mi serve!... E se anche non mi servisse più e potrei dunque smettere di usarlo, non ci vorrei onestamente nemmeno pensare a fare una cosa del genere, dopo tutto il lavoro! 😖</p>
<p>Comunque, per dirla bene, Listed è parecchio scarno: permette appena di inserire CSS personalizzato, e di formattare i post in Markdown e un sottoinsieme ristretto di HTML (di cui propongo di seguito le mie analisi): </p>
<p>Comunque, per dirla bene, Listed è parecchio scarno: permette appena di inserire CSS personalizzato, e di formattare i post in Markdown e un sottoinsieme ristretto di HTML (di cui propongo di seguito le mie analisi):</p>
<ul>
<li>Tutti gli elementi che non sono <code class="prettyprint">&lt;p&gt;</code> vengono automaticamente inseriti in uno di quelli (ad esempio, non si può avere <code class="prettyprint">&lt;body&gt;&lt;span&gt;Erre&lt;/span&gt;&lt;/body&gt;</code>, sarà sempre <code class="prettyprint">&lt;body&gt;&lt;p&gt;&lt;span&gt;Erre&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;</code>);</li>
<li>Qualsiasi attributo di elemento che non sia <code class="prettyprint">id</code>, <code class="prettyprint">class</code>, <code class="prettyprint">style</code>, <code class="prettyprint">href</code>, o <code class="prettyprint">src</code> viene buttato via: scordatevi quindi, per dire, di deviare dalla configurazione predefinita della piattaforma per quanto riguarda il comportamento dei link (il <code class="prettyprint">rel</code> non si tocca);</li>
<li>Certi elementi proprio non vanno: quelli con tag non-standard (custom insomma, tipo <code class="prettyprint">&lt;pincopallino&gt;</code>) sono eliminati, così come<code class="prettyprint">&lt;script&gt;</code>, <code class="prettyprint">&lt;link&gt;</code>, e a quanto pare pure roba come <code class="prettyprint">&lt;video&gt;</code> - però <code class="prettyprint">&lt;iframe&gt;</code>, per dire, funge, e per fortuna anche il mio adorato <code class="prettyprint">&lt;details&gt;</code>. </li>
<li>Tutti gli elementi che sono di loro natura inline vengono automaticamente inseriti in un blocco <code class="prettyprint">&lt;p&gt;</code> (ad esempio, non si può avere <code class="prettyprint">&lt;body&gt;&lt;span&gt;Erre&lt;/span&gt;&lt;/body&gt;</code>, sarà sempre <code class="prettyprint">&lt;body&gt;&lt;p&gt;&lt;span&gt;Erre&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;</code>);</li>
<li>Qualsiasi attributo di elemento che non sia <code class="prettyprint">id</code>, <code class="prettyprint">class</code>, o <code class="prettyprint">style</code>, (ed <code class="prettyprint">href</code> o <code class="prettyprint">src</code>, così come credo roba come <code class="prettyprint">title</code>, <code class="prettyprint">width</code>, o <code class="prettyprint">height</code>, nei casi opportuni) viene buttato via: scordatevi quindi, per dire, di deviare dalla configurazione predefinita della piattaforma per quanto riguarda il comportamento dei link (il <code class="prettyprint">rel</code> non si tocca);</li>
<li>Certi elementi proprio non vanno: quelli con tag non-standard (custom insomma, tipo <code class="prettyprint">&lt;pincopallino&gt;</code>) sono eliminati, così come<code class="prettyprint">&lt;script&gt;</code>, <code class="prettyprint">&lt;link&gt;</code>, e a quanto pare pure roba come <code class="prettyprint">&lt;video&gt;</code> - però <code class="prettyprint">&lt;iframe&gt;</code>, per dire, funge, e per fortuna anche il mio adorato <code class="prettyprint">&lt;details&gt;</code>.</li>
</ul>
<p>È chiarissimo che non può sostituire al 100% assolutamente nessuna sezione del sitoctt, nemmeno quella del MicroBlog, e anche se potesse andrebbe a creare frammentazione: il sito principale non conterrebbe più tutti tutti i post in modo centralizzato, e chi ne avesse interesse dovrebbe quindi seguire ben 2 siti diversi.</p>
<p>Mi stavo <em>quasi</em> per rassegnare, ma per fortuna ho il lampo di genio: <em>Ma posso scrivere su Standard Notes, ed avere le note che in automatico si sincronizzano come file sul sito? ...No, non c'è verso. No, aspe, e chi lo ha detto? Io dico che c'è verso.</em></p>
<p>Mi stavo <em>quasi</em> per rassegnare, ma per fortuna ho il lampo di genio: <em>Ma posso scrivere su Standard Notes, ed avere le note che in automatico si sincronizzano come file sul sito? ...No, non c'è verso. No, aspè, e chi lo ha detto? Io dico che c'è verso.</em></p>
<p>Ora, Standard Notes, come ho detto, usa un sistema di cifratura client-side dei dati, quindi per accedere alle note dal proprio account va usato un software un minimo specializzato.<br><br>
<p>Ora, Standard Notes, come ho detto, usa un sistema di cifratura client-side dei dati, quindi per accedere alle note dal proprio account va usato un software un minimo specializzato.<br>
C'è <em>sn-cli</em>, il client ufficiale a riga di comando, che potrebbe essere utile per creare script... però <a href="https://github.com/jonhadfield/sn-cli/issues/33" rel="noopener nofollow" target="_blank">pare proprio sia stato abbandonato</a>: niente da fare.</p>
<p>E allora, anche qui io quindi parlo, e dico: <em>Chi lo ha detto che devo fare le cose per bene e usare le API "vere"?</em><br><br>
Io dico che, visto che per qualche motivo l'HTML di un blog su Listed (nello specifico, la pagina <a href="https://listed.to/@u8/all">/all</a>, l'unica che mi sembra includere davvero tutto, e non solo gli ultimi X posts) include come stringa JSON i dati di tutti i post, allora io posso semplicemente pubblicare su Listed da Standard Notes ed avere un programma che fa lo sporco - ma in questo caso davvero semplice, visto che deve solo scaricare e parsare 1 file - lavoro dello scraping.</p>
<p>E allora, anche qui io quindi parlo, e dico: <em>Chi lo ha detto che devo fare le cose per bene e usare le API "vere"?</em><br>
Io dico che, visto che per qualche motivo l'HTML di un blog su Listed (nello specifico, la pagina <a href="https://listed.to/@u8/all">/all</a>, l'unica che mi sembra includere davvero tutto, e non solo gli ultimi X post) include come stringa JSON i dati di tutti i post, allora io posso semplicemente pubblicare su Listed da Standard Notes ed avere un programma che fa lo sporco - ma in questo caso davvero semplice, visto che deve solo scaricare e parsare 1 file - lavoro dello scraping.</p>
<p><em>E script fu scripto.</em> Lo lascio qui: <a href="https://gitlab.com/octtspacc/sitoctt/-/blob/main/Scripts/ListedDownsync.js" rel="noopener nofollow" target="_blank">https://gitlab.com/octtspacc/sitoctt/-/blob/main/Scripts/ListedDownsync.js</a>; attenzione però che non c'è niente di bello da leggere, anche perché non ho ancora implementato tutto quello che dovrebbe servirmi a lungo termine, ma per chi vuole usarlo sta là.<br><br>
<p><em>E script fu scripto.</em> Lo lascio qui: <a href="https://gitlab.com/octtspacc/sitoctt/-/blob/main/Scripts/ListedDownsync.js" rel="noopener nofollow" target="_blank">gitlab.com/octtspacc/sitoctt/Scripts/ListedDownsync.js</a>; attenzione però che non c'è niente di bello da leggere, anche perché non ho ancora implementato tutto quello che dovrebbe servirmi a lungo termine, ma per chi vuole usarlo sta là.<br>
È tecnicamente indipendente dal sitoctt e da staticoso, in quanto salva semplici file Markdown su disco (anche se la sintassi delle righe di metadati in essi scritti è quella mia; per cui, in caso, andrebbe cambiata qualche stringa di testo nel programma per supportare il più classico formato dei metadati YAML).</p>
<p>Non voglio dire che ho pensato davvero a tutto, però ci sono andata vicina.<br><br>
No non mi so ancora decidere - per colpa di mie paturnie - su che standard usare per indicare blocchi speciali (di metadati, o di HTML da non far interpretare al SSG di Listed).<br><br>
No, non ho ancora fatto lo script da avviare come cronjob sul server, che ogni tanto dovrebbe scaricare i dati dal blog di Listed e aggiornare i file nella repository Git se ci sono aggiornamenti.<br><br>
<p>Non voglio dire che ho pensato davvero a tutto, però ci sono andata vicina.<br>
No non mi so ancora decidere - per colpa di mie paturnie - su che standard usare per indicare blocchi speciali (di metadati, o di HTML da non far interpretare al SSG di Listed).<br>
No, non ho ancora fatto lo script da avviare come cronjob sul server, che ogni tanto dovrebbe scaricare i dati dal blog di Listed e aggiornare i file nella repository Git se ci sono aggiornamenti.<br>
Però, ho pensato alle cose veramente inderogabili, tipo la possibilità per il programma di sincronizzazione di non sovrascrivere un file già nella repo se questo specifica la cosa nei metadati. Metti che succede un qualunque casino, e io devo modificare i dati del post per il sitoctt senza toccare quelli della nota su Standard Notes: impostando questa flag, lo script di sincronizzazione non lo sostituirà la prossima volta che gira.</p>
<p>Come concludere, dunque.<br><br>
Oggi ho già scritto troppo, e la chiudo qui. Però spero vivamente che tutta questa nuova efficienza e comodità, l'avere ulteriore tecnologia che lavora per me, e non obbliga me a lavorare per lei <em>(in teoria, tanto i problemi devono uscire fuori, altrimenti io non sono io!)</em>, possa farmi scrivere di più e con più piacere. Che sia sul <a href="https://sitoctt.octt.eu.org" rel="noopener nofollow" target="_blank">sitoctt</a>, o sulle lastre di pietra.</p>
<p>Ah, si, questo qui è stato il primo post sul <a href="https://listed.to/@u8">mio blog Listed</a>, è giusto precisarlo per chi legge dal sitoctt.</p>
<p>Come concludere, dunque.<br>
Oggi ho già scritto troppo, e la chiudo qui. Però spero vivamente che tutta questa nuova efficienza e comodità, l'avere ulteriore tecnologia che lavora per me, e non obbliga me a lavorare per lei <em>(in teoria, tanto comunque i problemi devono uscire fuori, altrimenti io non sono io!)</em>, possa farmi scrivere di più e con più piacere. Che sia sul <a href="https://sitoctt.octt.eu.org" rel="noopener nofollow" target="_blank">sitoctt</a>, o sulle lastre di pietra.</p>
<p>Ah, si, questo qui è stato il primo post sul <a href="https://listed.to/@u8">mio blog Listed</a>, è giusto precisarlo per chi legge dal sitoctt... ma io spero che almeno la versione Listed del blog possa venire indicizzata per bene e scoperta da più persone, già che ci siamo.</p>

View File

@ -0,0 +1,9 @@
/**
* Created by Erxin, Shang(Edwin) on 6/21/2016.
* JavaScript Configuration file(.ini) content parser, similar to python ConfigParser without I/O operations
* The license is under GPL-3.0
* Git repo:https://github.com/shangerxin/config-ini
* Author homepage: http://www.shangerxin.com
* Version, 1.6.1
*/
!function(t){var n=Object.assign(new Error,{name:"ConfigIniParser Error",message:"Parse config ini file error"}),r=Object.assign(new Error,{name:"ConfigIniParser Error",message:"The specify section not found"}),e=Object.assign(new Error,{name:"ConfigIniParser Error",message:"The specify option not found"}),i=Object.assign(new Error,{name:"ConfigIniParser Error",message:"Found duplicated section in the given ini file"}),o=Object.assign(new Error,{name:"ConfigIniParser Error",message:"Multiple call parse on the same parser instance"}),s=Object.assign(new TypeError,{name:"ConfigIniParser Error",message:"The argument type is not correct"}),a="__DEFAULT_SECTION__",u=/^\s*\[\s*([^\]]+?)\s*\]\s*$/,p=/\s*([^=:\s]+)\s*[=:]\s*(.*)\s*/,f=/^\s*[#;].*/,c=/^\s*$/,h=-1;function l(t,n){for(var r=t.sections,e=0;e<r.length;e++){var i=r[e];if(i.name==n)return i}}function m(t,n){for(var r=t.options,e=0;e<r.length;e++){var i=r[e];if(i.name==n)return i}}function g(t){return{name:t,options:[]}}function v(t,n){return{name:t,value:n}}var y=function(t){this.delimiter=t||"\n",this._calledParse=!1,this._ini={sections:[]},this._ini.sections.push(g(a))};y.prototype.addSection=function(t){if(l(this._ini,t))throw i;var n=g(t);return this._ini.sections.push(n),this},y.prototype.get=function(t,n,r){var i=l(this._ini,t||a);if(i){var o=m(i,n);if(o)return o.value}if(void 0===r)throw e;return r},y.prototype.getOptionFromDefaultSection=function(t,n){return this.get(null,t,n)},y.prototype.getBoolean=function(t,n){var r=this.get(t||a,n);return isNaN(r)?"true"==String(r).toLowerCase():0!=r},y.prototype.getBooleanFromDefaultSection=function(t){return this.getBoolean(null,t)},y.prototype.getNumber=function(t,n){return+this.get(t||a,n)},y.prototype.getNumberFromDefaultSection=function(t){return this.getNumber(null,t)},y.prototype.isHaveSection=function(t){if(!t)throw s;return!!l(this._ini,t)},y.prototype.isHaveOption=function(t,n){var r=l(this._ini,t||a);if(r&&m(r,n))return!0;return!1},y.prototype.isHaveOptionInDefaultSection=function(t){return this.isHaveOption(null,t)},y.prototype.items=function(t){for(var n=l(this._ini,t||a),r=[],e=0;e<n.options.length;e++){var i=n.options[e];r.push([i.name,i.value])}return r},y.prototype.options=function(t){var n=l(this._ini,t||a);if(n){for(var e,i=[],o=n.options,s=0;s<o.length;s++)e=o[s],i.push(e.name);return i}throw r},y.prototype.removeOption=function(t,n){var r=l(this._ini,t||a);if(r){var e=function(t,n){for(var r=t.options,e=0;e<r.length;e++)if(r[e].name==n)return e;return h}(r,n);if(e!=h)return r.options.splice(e,1),!0}return!1},y.prototype.removeOptionFromDefaultSection=function(t){return this.removeOption(null,t)},y.prototype.removeSection=function(t){var n=function(t,n){for(var r=t.sections,e=0;e<r.length;e++)if(r[e].name==n)return e;return h}(this._ini,t||a);return n!=h&&(this._ini.sections.splice(n,1),t===a&&this._ini.sections.push(g(a)),!0)},y.prototype.sections=function(){for(var t,n=[],r=this._ini.sections,e=0;e<r.length;e++)(t=r[e]).name!=a&&n.push(t.name);return n},y.prototype.set=function(t,n,e){var i,o=l(this._ini,t||a);if(o)return(i=m(o,n))?(i.value=e,this):(i=v(n,e),o.options.push(i),this);throw r},y.prototype.setOptionInDefaultSection=function(t,n){return this.set(null,t,n)},y.prototype.stringify=function(t){for(var n,r,e,i=[],o=this._ini.sections,s=0;s<o.length;s++){(n=o[s]).name!=a&&i.push("["+n.name+"]"),r=n.options;for(var u=0;u<r.length;u++)e=r[u],i.push(e.name+"="+e.value);i.length>0&&i.push("")}return i.join(t||this.delimiter)},y.prototype.parse=function(t){if(this._calledParse)throw o;this._calledParse=!0;for(var r=t.split(this.delimiter),e=l(this._ini,a),s=0;s<r.length;s++){var h=r[s];if(!h.match(f)&&!h.match(c)){var m=h.match(u);if(m){var y=m[1];if(l(this._ini,y))throw i;e=g(y),this._ini.sections.push(e)}else{var _=h.match(p);if(!_)throw n;var E=v(_[1],_[2]);e.options.push(E)}}}return this},y.Errors={Error:n,ErrorNoSection:r,ErrorNoOption:e,ErrorDuplicateSectionError:i,ErrorCallParseMultipleTimes:o,ErrorIncorrectArgumentType:s},t.ConfigIniParser=y}("undefined"!=typeof exports?exports:"undefined"!=typeof window?window:{});

View File

@ -1,20 +1,21 @@
#!/usr/bin/env -S node --experimental-fetch
const fs = require('fs');
const JSDOM = require('jsdom').JSDOM;
const ConfigParser = require("./Lib/config-ini-parser").ConfigIniParser;
const BlogURL = 'https://listed.to/@u8';
const BlogURL = 'https://listed.to/@u8'; // Full base URL of the Listed blog (any server)
const SiteName = 'sitoctt';
//const DefaultMode = 'Include' // 'Include' or 'Exclude' | Not implemented
const PostsFileDate = true; // Append date to posts file names
const Replacements = {
const PostsFileDate = true; // Append dates (YYYY-MM-DD) to posts file names
const Replacements = { // Format: { ReplaceWithString: [ToFindString] }
"<a href=\"[staticoso:CustomPath:Assets]/": "<a href=\"https://sitoctt-assets.octt.eu.org/",
"<img src=\"[staticoso:CustomPath:Assets]/": "<img src=\"https://sitoctt-assets.octt.eu.org/",
//"[staticoso:CustomPath:Assets]/": [
// "<a href=\"https://sitoctt-assets.octt.eu.org/",
// "<img src=\"https://sitoctt-assets.octt.eu.org/"
//]
// TODO: Fix anchor rels
};
const MetadataBlockSelect = '.MetadataBlock, .MetadataBlock + :Where(Div, Pre, Code)';
const ExtractCodeBlockSelect = '.ExtractCodeBlock, .ExtractCodeBlock + :Where(Div, Pre, Code)';
const TryReadFileSync = Path => {
if (fs.existsSync(Path)) {
return fs.readFileSync(Path, 'utf8');
@ -22,7 +23,7 @@ const TryReadFileSync = Path => {
};
const TryMkdirSync = Path => {
if (!fs.existsSync(Path)) {
return fs.mkdirSync(Path, {recursive:true});
return fs.mkdirSync(Path, {recursive: true});
};
};
@ -33,6 +34,12 @@ const GetPath = URL => {
return URL;
};
const GetFragHTML = Frag => {
let Dom = new JSDOM('<body></body>');
Dom.window.document.body.appendChild(Frag);
return Dom.window.document.body.innerHTML;
};
const CheckDoDownsync = File => {
let DoDownsync = true;
const TryFile = TryReadFileSync(File);
@ -41,11 +48,10 @@ const CheckDoDownsync = File => {
for (let i=0; i<Lines.length; i++) {
const Line = Lines[i].trim().replaceAll(' ', ' ').replaceAll(':', ' : ').replaceAll('=', ' = ');
if (Line.startsWith('// ')) {
const Tokens = Line.split(' ').filter(e => {return e != ''});
if (Tokens[1] == '%' && Tokens[2] == 'downsync') {
if (['false', 'disabled', 'off', 'no'].includes(Tokens[4])) {
DoDownsync = false;
};
const Tokens = Line.split(' ').filter(i => {return i != ''});
if (Tokens[1] == '%' && Tokens[2] == 'downsync' && [':', '='].includes(Tokens[3]) && ['false', 'disabled', 'off', 'no'].includes(Tokens[4])) {
DoDownsync = false;
break;
};
};
};
@ -53,27 +59,89 @@ const CheckDoDownsync = File => {
return DoDownsync;
};
const HandlePost = PostSrc => {
const GetLinkElem = Dom => {
let Elem;
let Post = {};
Post['Title'] = PostSrc.title;
Post['CreatedOn'] = PostSrc.created_at.split('T')[0];
console.log(`[I] => [${Post['CreatedOn']}] ${Post['Title']}`);
const Dom = JSDOM.fragment(PostSrc.rendered_text);
Elem = Dom.querySelector(`.Mirror-${SiteName}`);
if (!Elem) {
Elem = Dom.querySelector(`.Mirror-${SiteName}-Include`);
};
if (!Elem) { // Post content has no mirror-flagging element, skip it
console.log(`[I] : No mirror flag in source body; Skipping!`);
return; // TODO: Exclusion mode instead of inclusion? Aka automatically handle posts without the element
};
return Elem;
};
const Path = GetPath(JSDOM.fragment(Elem.outerHTML).querySelector('[href]').href);
Post['Categories'] = '';
const Classes = Elem.classList;
const ParseMeta = Raw => {
let Mid = {"Meta": "", "Macros": ""};
let Data = {"Meta": {}, "Macros": {}};
const Lines = Raw.trim().split('\n');
for (let i=0; i<Lines.length; i++) {
let Type;
const Line = Lines[i].trim();
if (Line.startsWith('%')) {
Type = 'Meta';
} else if (Line.startsWith('$')) {
Type = 'Macros';
} else {
continue;
};
Mid[Type] += Line.substring(1).trim() + '\n';
};
const Types = Object.keys(Mid);
for (let i=0; i<Types.length; i++) {
const Type = Types[i];
Parser = new ConfigParser();
Parser.parse(Mid[Type]);
const Items = Parser.items();
for (let i=0; i<Items.length; i++) {
const Item = Items[i];
Data[Type][Item[0]] = Item[1];
};
};
return Data;
};
const MakeMetaStr = Post => {
let Str = '';
const Types = ['Meta', 'Macros'];
for (let i=0; i<Types.length; i++) {
let Mark;
const Type = Types[i];
if (Type == 'Meta') {
Mark = '%';
} else if (Type == 'Macros') {
Mark = '$';
};
const Keys = Object.keys(Post[Type]);
for (let i=0; i<Keys.length; i++) {
const Key = Keys[i];
Str += `// ${Mark} ${Key} = ${Post[Type][Key]}\n`
};
};
return Str;
};
const HandlePost = PostSrc => {
let LinkElem, ContentDom;
let Post = {"Meta": {}, "Macros": {}};
Post.Meta.Title = PostSrc.title;
Post.Meta.CreatedOn = PostSrc.created_at.split('T')[0];
Post.Content = PostSrc.rendered_text;
console.log(`[I] => [${Post.Meta.CreatedOn}] ${Post.Meta.Title}`);
ContentDom = JSDOM.fragment(Post.Content);
LinkElem = GetLinkElem(ContentDom);
if (!LinkElem) { // Post content has no mirror-flagging element, skip it
// TODO: Exclusion mode instead of inclusion? Aka automatically handle posts without the element
// TODO: Check flagging via MetadataBlock?
console.log(`[I] : No mirror flag in source body; Skipping!`);
return;
};
const LinkPath = GetPath(JSDOM.fragment(LinkElem.outerHTML).querySelector('[href]').href);
/*
// Get post categories
Post.Categories = '';
const Classes = LinkElem.classList;
for (let i=0; i<Classes.length; i++) {
const Class = Classes[i];
const Key = `Mirror-${SiteName}-Categories-`;
@ -81,8 +149,9 @@ const HandlePost = PostSrc => {
Post['Categories'] = '// % Categories = ' + Class.substring(Key.length).replaceAll('|', ' ');
};
};
*/
Post['Content'] = PostSrc.rendered_text.replace(Elem.outerHTML, '');
// Do string replacements
const ReplacementsKeys = Object.keys(Replacements);
for (let i=0; i<ReplacementsKeys.length; i++) {
const To = ReplacementsKeys[i];
@ -91,25 +160,56 @@ const HandlePost = PostSrc => {
FromItems = [FromItems];
};
for (let i=0; i<FromItems.length; i++) {
Post['Content'] = Post['Content'].replaceAll(FromItems[i], To);
Post.Content = Post.Content.replaceAll(FromItems[i], To);
};
};
const PathFile = Path.split('/').slice(-1)[0];
const PathDir = Path.split('/').slice(0, (Path.split('/').length - 1)).join('/');
const FinalFilePath = `${PathDir}/${PostsFileDate ? Post['CreatedOn']+'-' : ''}${PathFile.substring(0, (PathFile.length - 4))}md`;
ContentDom = JSDOM.fragment(Post.Content);
LinkElem = GetLinkElem(ContentDom);
LinkElem.outerHTML = '';
// Handle MetadataBlock elements
let MetadataBlocks = ContentDom.querySelectorAll(MetadataBlockSelect);
for (let i=0; i<MetadataBlocks.length; i++) {
const Elem = MetadataBlocks[i];
if (Elem.textContent) {
const Meta = ParseMeta(Elem.textContent);
Post.Meta = Object.assign(Post.Meta, Meta.Meta);
Post.Macros = Object.assign(Post.Macros, Meta.Macros);
};
MetadataBlocks[i].outerHTML = '';
};
// Handle ExtractCodeBlock elements
let ExtCodeBlocks = ContentDom.querySelectorAll(ExtractCodeBlockSelect);
for (let i=0; i<ExtCodeBlocks.length; i++) {
const Elem = ExtCodeBlocks[i];
const Find = ExtractCodeBlockSelect.trim().replaceAll('.', '').replaceAll(',', '').split(' ')[0];
if (Array.from(Elem.classList).includes(Find)) {
ExtCodeBlocks[i].outerHTML = ''; // Remove the ExtractCodeBlock upper-marker
} else {
ExtCodeBlocks[i].outerHTML = Elem.textContent; // Extract the marker's text as raw HTML
};
};
Post.Content = GetFragHTML(ContentDom).trim();
const PathFile = LinkPath.split('/').slice(-1)[0];
const PathDir = LinkPath.split('/').slice(0, (LinkPath.split('/').length - 1)).join('/');
const FinalFilePath = `${PathDir}/${PostsFileDate ? Post.Meta.CreatedOn + '-' : ''}${PathFile.substring(0, (PathFile.length - 4))}md`;
if (!CheckDoDownsync(FinalFilePath)) {
console.log(`[I] : Downsync disabled in destination body; Skipping!`);
return;
};
TryMkdirSync(PathDir);
fs.writeFileSync(FinalFilePath, `\
${Post['Categories']}
// % CreatedOn = ${Post['CreatedOn']}
${MakeMetaStr(Post)}
# ${Post.Meta.Title}
# ${Post['Title']}
${Post['Content']}
${Post.Content}
`);
};