diff options
828 files changed, 41310 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..4dd74ab --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Bloggings + +This is my personal minimalist blog written in php and bash. + +It has no Javascript, cookies, trackers, or bloat, and can be viewed in any browser. + +It is also validated using the [smolweb validator](https://smolweb.org/validator/) diff --git a/articles/2021/Alacritty.html b/articles/2021/Alacritty.html new file mode 100755 index 0000000..a31d039 --- /dev/null +++ b/articles/2021/Alacritty.html @@ -0,0 +1,37 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Alacritty</h2> +<h3>2021-05-12</h3> +<p><img src="/images/alacritty.webp" alt="image alacritty"></p> +<p>Lorsque j'ai commencé à utiliser i3 la semaine dernière, +j'ai opté d'utiliser xfce4-terminal pendant que je farfouillais. +Aujourd'hui, je suis passé à Alacritty. Il est écrit en Rust, et est très intéressant.</p> +<p>Par exemple , vous pouvez sélectionner du texte dans le terminal sans souris. +Démarrez le mode vi avec Ctrl+Shift+Space et vous pouvez déplacer le curseur avec +les flèches ou hjkl. Pour démarrer la sélection, appuyez sur v, pour copier, +appuyez sur y, pour coller, appuyez sur Ctrl+Shift+v. +Le texte copié va dans le presse-papiers primaire.</p> +<p>Si vous utilisez Ranger comme gestionnaire de fichiers, vous pouvez visualiser les images +dans le terminal Alacritty, et c'est vraiment rapide, je peux feuilleter ma collection +de photos sur mon NAS Synology (monté via cifs) avec facilité.</p> +<p>Installer avec <b>pacman -S alacritty</b> sur Manjaro ou Arch Linux</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Arch-LTS-kernel.html b/articles/2021/Arch-LTS-kernel.html new file mode 100755 index 0000000..095ecd9 --- /dev/null +++ b/articles/2021/Arch-LTS-kernel.html @@ -0,0 +1,45 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Arch LTS kernel</h2> +<h3>2021-05-28</h3> +<p>Arch Linux est parfois étiqueté comme étant instable, +mais en réalité ce sont les options de pointe qui peuvent +le rendre instable. J'ai opté pour la stabilité, j'utilise +donc le noyau LTS.</p> +<p>Installer le noyeau LTS</p> +<pre>sudo pacman -S linux-lts +sudo grub-mkconfig -o /boot/grub/grub.cfg +sudo reboot +</pre> +<p>Retirer le noyeau de pointe</p> +<pre>sudo pacman -R linux +sudo grub-mkconfig -o /boot/grub/grub.cfg +sudo reboot +</pre> +<p> +J'ai dû supprimer acpi_call et tp-smapi avant de +retirer le noyau de pointe, et les réinstaller en +utilisant les versions DKMS dans l'AUR. +</p> +<p><a href="https://averagelinuxuser.com/the-lts-kernel-in-arch-linux/">Why and How to install the LTS kernel in Arch +Linux</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Arch-Linux.html b/articles/2021/Arch-Linux.html new file mode 100755 index 0000000..5803339 --- /dev/null +++ b/articles/2021/Arch-Linux.html @@ -0,0 +1,36 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Arch Linux</h2> +<h3>2021-05-28</h3> +<p><img src="/images/arch-logo.webp" alt="arch logo"></p> +<p>Banzaï! Je suis passé de Manjaro Linux à <a href="https://archlinux.org/">Arch Linux</a> +sur mon X220</p> +<p>Manjaro est basé sur Arch, +et est génial pour les nouveaux utilisateurs, mais je voulais la vraie +article.</p> +<p>J'ai un peu triché en utilisant l'installateur +<a href="https://sourceforge.net/projects/blue-arch-installer/">calamares</a> +pour éviter 4 jours de configuration manuelle. Bien que vous deviez encore +effectuer un certain nombre d'étapes après avoir utilisé calamares pour +avoir une jolie Linux, l'installation de base est effectuée proprement.</p> +<p>J'ai maintenant une installation Arch pure avec le windowmanager i3, Firefox, Telegram, Neomutt, etc.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Bibliothèque-Nationale-de-France.html b/articles/2021/Bibliothèque-Nationale-de-France.html new file mode 100755 index 0000000..35ae03b --- /dev/null +++ b/articles/2021/Bibliothèque-Nationale-de-France.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Bibliothèque Nationale de France</h2> +<h3>2021-04-07</h3> +<p>La BNF est vraiment une mine d'or. J'étais à la recherche d'informations historiques via Gallica. J'ai trouvé les données très bien présentées et très agréables à utiliser. Voici un exemple de recherche +: <a href="https://gallica.bnf.fr/ark:/12148/bpt6k56069257/f15.item.r=collin%C3%A9e" target="blank">Gallica</a></p> +<p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Bol-en-chêne-de-28cm.html b/articles/2021/Bol-en-chêne-de-28cm.html new file mode 100755 index 0000000..91f7751 --- /dev/null +++ b/articles/2021/Bol-en-chêne-de-28cm.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Bol en chêne de 28cm</h2> +<h3>2021-05-14</h3> +<p><img src="/images/oak-bowl-28cm.webp" alt="image"></p> +<p> +De nouveau dans le garage. Fabriqué avec du chêne que mon ami Joël m'a donné. +</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Boxe-pour-chevaux-jouets.html b/articles/2021/Boxe-pour-chevaux-jouets.html new file mode 100755 index 0000000..bd19b12 --- /dev/null +++ b/articles/2021/Boxe-pour-chevaux-jouets.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Boxe pour chevaux jouets</h2> +<h3>2021-12-16</h3> +<p><img src="/images/box-chevaux.webp" alt="image"></p> +<p>le père noël fait des heures supplémentaires.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Ce-que-l'Europe-à-fait-pour-moi.html b/articles/2021/Ce-que-l'Europe-à-fait-pour-moi.html new file mode 100755 index 0000000..1d6732b --- /dev/null +++ b/articles/2021/Ce-que-l'Europe-à-fait-pour-moi.html @@ -0,0 +1,37 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Ce que l'Europe à fait pour moi</h2> +<h3>2021-05-09</h3> +<p><img src="/images/eu-flag.webp" alt="Eu Flag"></img></p> +<p>Sans l'Union européenne, je n'aurais pas pu vivre et prospérer en France depuis 1995. +Voyager à travers l'Europe pendant mon adolescence m'a ouvert les yeux sur d'autres façons +de vivre et a calmé mon cerveau turbulent. J'ai bénéficié d'une vie sans guerre +(pour laquelle l'UE a reçu le prix Nobel), de la possibilité de voyager et de travailler +librement dans d'autres pays de l'UE, d'une économie stable grâce à l'euro, +et d'un environnement autre que le piège mortel du nationalisme.</p> +<p>Joyeux anniversaire l'Europe!</p> +<ul> +<li><a href="https://www.europe.bzh/">L'Europe en Bretagne</a></li> +<li><a href="https://europa.eu/european-union/about-eu/what-the-eu-does-for-its-citizens_fr">Ce que l'UE fait pour ses +citoyens</a></li> +</ul> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Certbot-delete.html b/articles/2021/Certbot-delete.html new file mode 100755 index 0000000..1ccf064 --- /dev/null +++ b/articles/2021/Certbot-delete.html @@ -0,0 +1,30 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Certbot delete</h2> +<h3>2021-06-03</h3> +<p>Ce matin, j'ai remarqué que le renouvellement des certificats letsencrypt +sur mon serveur de messagerie a avorté à cause d'un domaine qui n'existe plus. +La suppression manuel du domaine dans /etc/letsencrypt a aggravé la situation, +mais j'ai finalement découvert qu'il existe une commande simple pour le faire +:</p> +<p><code>certbot delete</code></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Coupure-microphone-X220.html b/articles/2021/Coupure-microphone-X220.html new file mode 100755 index 0000000..9f465b9 --- /dev/null +++ b/articles/2021/Coupure-microphone-X220.html @@ -0,0 +1,53 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Coupure microphone X220</h2> +<h3>2021-05-09</h3> +<p>Dans mon Thinkpad X22O fonctionnant sous Manjaro i3, la bouton de coupure du micro est +désactivée, car il n'est pas pris en charge par thinkpad-acpi.<br> +Pour l'activer, voici ce que j'ai fait:</p> +<p>Premièrement, vérifier que l'acpi est installé:</p> +<pre>sudo pacman -S acpi</pre> +</p>1) créer le fichier /etc/acpi/events/lenovo-mutemic</p> +<pre> +event=bouton/f20 +action=/etc/acpi/lenovo-mutemic.sh +</pre> +<p>2) créez le fichier /etc/acpi/lenovo-mutemic.sh</p> +<pre> +#!/bin/bash +INPUT_DEVICE=" 'Capture'" +VOTRE_USERNAME="philip" +if amixer sget $INPUT_DEVICE,0 | grep '\[on\]' ; then + amixer sset $INPUT_DEVICE,0 toggle + echo "0 blink" | sudo tee /proc/acpi/ibm/led + DISPLAY=":0.0" notify-send -t 1000 -i microphone-sensibilité-muted-symbolic "Mic MUTED" +else + amixer sset $INPUT_DEVICE,0 toggle + DISPLAY=":0.0" notify-send -t 1000 -i microphone-sensitivity-high-symbolic "Mic ON" + echo "0 on" | sudo tee /proc/acpi/ibm/led +fi +</pre> +<p>3) redémarrer acpi</p> +<pre>systemctl restart acpid.service</pre> +<p>Adapté de <a +href="https://askubuntu.com/questions/125367/enabling-mic-mute-button-and-light-on-lenovo-thinkpads">réponseAskubuntu</a>.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Deux-maisons-de-poupées.html b/articles/2021/Deux-maisons-de-poupées.html new file mode 100755 index 0000000..e0934f1 --- /dev/null +++ b/articles/2021/Deux-maisons-de-poupées.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Deux maisons de poupées</h2> +<h3>2021-12-06</h3> +<p><img src="/images/maison-poupee.webp" alt="image"></p> +<p>Deux maisons de poupées pour deux sœurs, l'une droitière, l'autre gauchère. Je les ai fabriquées à partir d'étagères en pin 18mm bon marché et de quelques chutes de bois +dur.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Expériment.html b/articles/2021/Expériment.html new file mode 100755 index 0000000..ce22529 --- /dev/null +++ b/articles/2021/Expériment.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Expériment</h2> +<h3>2021-08-02</h3> +<p><img src="/images/tournages/expérimentation.webp" alt="expérimentation"></p> +<p> +Je joue avec différentes formes pour voir ce que je peux faire. Ce morceau de chêne a été tourné deux fois, avec 5 mois de séchage entre les deux tournages. +</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Fabriquez-votre-propre-ordinateur-portable.htm b/articles/2021/Fabriquez-votre-propre-ordinateur-portable.htm new file mode 100755 index 0000000..009ce74 --- /dev/null +++ b/articles/2021/Fabriquez-votre-propre-ordinateur-portable.htm @@ -0,0 +1,53 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + <!-- article start --> +<h2>Fabriquez votre propre ordinateur portable?</h2> +<h5>2021-05-11</h5> +<p><img src="/images/classic-keyboard.webp" alt="classic keyboard photo"/></p> +<p>Aujourd'hui, nous sommes tous victimes d'ordinateurs portables ultraminces avec des claviers de +pacotille, des boîtiers en plastique bon marché et l'impossibilité de les mettre à niveau en +raison de la soudure de la RAM sur la carte mère, ainsi que la batterie, +etc.</p> +<p>Il est si difficile de trouver un ordinateur portable avec un bon clavier que +les anciens ordinateurs portables tels que les Thinkpads X220, T420, W520 etc. +s'arrachent dans le monde entier. Oui, un X220 de 10 ans vaut son pesant d'or.</p> +<p>Pourquoi cela s'est-il produit ? L'avidité - supprimer des choses pour augmenter la marge bénéficiaire +sur le dos d'achéteurs peu méfiantes, répondre aux demandes de fashionistas +(je vous regarde, Apple) plutôt qu'à la qualité d'utilisation et de mise à jour ne sont que deux raisons. +Au diable ceux qui veulent des solutions écologiques à longue durée de vie.</p> +<p>Cela ne laisse pas beaucoup d'espoir pour l'avenir, à moins que les ordinateurs portables +auto-assemblés ne deviennent plus populaires, +ou que la production de petites séries devienne plus facile et financièrement rentable.</p> +<p>Il y a peut-être de l'espoir, <a href="https://www.olimex.com/Products/DIY-Laptop/">Olitec</a> +par exemple est une entreprise bulgare qui fait exactement cela avec son ordinateur portable TERES. +Il y a aussi le <a href="https://www.pine64.org/pinebook/">Pinebook</a>. +J'en cherche d'autres et je les ajouterai dans ce blog lorsque je le trouve.</p> +<h3>References:</h3> +<ul> +<li><a href="https://youtu.be/Fzmm87oVQ6c">My personal fight against the modern laptop</a></li> +<li><a href="https://youtu.be/HgxWQojO2QI">Why a 9-year Old Laptop is Still Relevant in 2020</a></li> +</ul> + <!-- article end --> + <p> </p> + + <div class="center"> + A minimalist blog by + <a + href="mailto:%70%68%69%6C%69%70%40%77%69%74%74%61%6D%6F%72%65%2E%63%6F%6D" + >Philip Wittamore</a + > + </div> + </body> +</html> diff --git a/articles/2021/Fugitive-Telemetry.html b/articles/2021/Fugitive-Telemetry.html new file mode 100755 index 0000000..152500d --- /dev/null +++ b/articles/2021/Fugitive-Telemetry.html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Fugitive Telemetry</h2> +<h3>2021-04-29</h3> +<p><img src="/images/fugitive-telemetry.webp" alt="book cover"></p> +<p>J'ai pleinement apprécié cette série jusqu'à présent, +l'histoire est racontée par un cyborg qui est plus humain que les humains qui l'entourent. +J'espère que ce livre sera tout aussi bon. +</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/GeoIP2-et-Goaccess.html b/articles/2021/GeoIP2-et-Goaccess.html new file mode 100755 index 0000000..58c45a1 --- /dev/null +++ b/articles/2021/GeoIP2-et-Goaccess.html @@ -0,0 +1,33 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>GeoIP2 et Goaccess</h2> +<h3>2021-04-09</h3> +<p>Aujourd'hui, j'ai mis à jour <a href="https://goaccess.io/">goaccess</a> pour qu'il fonctionne avec GeoIP2. +J'ai d'abord téléchargé et installé la dernière version de geoipupdate, +puis créé un compte gratuit avec <a href="https://www.maxmind.com">Maxmind</a> +et obtenu un identifiant de compte et une clé de licence. +J'ai ajouté ceux-ci au fichier /etc/GeoIP.conf, et j'ai ajouté une entrée crontab avec +<code>@weekly /usr/local/bin/geoipupdate</code> +geoipupdate a mis à jour 2 fichiers dans /usr/share/GeoIP/.<br> +J'ai ensuite modifié goaccess.conf avec <code>geoip-database /usr/share/GeoIP/GeoLite2-City.mmdb</code> +et goaccess affiche maintenant les villes correspondant aux adresses IP.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Grande-vase-en-chêne.html b/articles/2021/Grande-vase-en-chêne.html new file mode 100755 index 0000000..faf9632 --- /dev/null +++ b/articles/2021/Grande-vase-en-chêne.html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Grande vase en chêne</h2> +<h3>2021-04-26</h3> +<p><img src="/images/grande-vase.webp" alt="image"></p> +<p>J'ai fabriqué ce grand vase à partir de couches de chêne récupéré. +J'ai percé des trous de 40 mm dans chaque couche sauf deux, +et j'ai fait un bouchon de 40 mm pour pouvoir le tourner entre les deux extrémités du +tour. Voir dans <a href="https://wittamore.fr/images/tournages/index.php">tournages</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Intel-Thermald.html b/articles/2021/Intel-Thermald.html new file mode 100755 index 0000000..10c6b6c --- /dev/null +++ b/articles/2021/Intel-Thermald.html @@ -0,0 +1,46 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Intel Thermald</h2> +<h3>2021-05-14</h3> +<p><img src="/images/core-i5-cpu.webp" alt="core i5 cpu image"></p> +<p>J'ai remarqué aujourd'hui que gcc a monté mon cpu i5 à +96° en compilant un paquet AUR. Le reconditionneur BackMarket m'a +assuré qu'il renouvelait la pâte thermique sur toutes ses machines, je +ne l'ai donc pas encore vérifiée. En cherchant une protection thermique +logicielle pour mon vénérable X220, je suis tombé sur <b>thermald</b>.</p> +<p>Depuis le wiki debian:</p> +<p><i>"Le démon thermique Linux (thermald) surveille et contrôle la température dans les +ordinateurs portables, les tablettes PC avec le dernier sandy bridge d'Intel +et les dernières versions de CPU Intel. Une fois que la température du +système atteint un certain seuil, le démon Linux active diverses méthodes +de refroidissement pour essayer de refroidir le système. " </i></p> +<p>Je ne sais pas si cela est suffisant, ou si une approche +plus compliquée utilisant cpupower sera nécessaire. Affaire à suivre..</p> +<p>Installation:</p> +<pre>sudo pacman -S thermald +sudo systemctl enable thermald +sudo systemctl start thermald +</pre> +<ul> +<li><a href="https://01.org/linux-thermal-daemon/documentation/introduction-thermal-daemon">Linux* Thermal Daemon</a></li> +</ul> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Invidious---une-interface-alternative-pour-Youtube.html b/articles/2021/Invidious---une-interface-alternative-pour-Youtube.html new file mode 100755 index 0000000..6cce186 --- /dev/null +++ b/articles/2021/Invidious---une-interface-alternative-pour-Youtube.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Invidious - une interface alternative pour Youtube</h2> +<h3>2021-04-17</h3> +<p>Invidious vous permet de visionner Youtube sans pistage ni publicité.</p> +<p>Vous pouvez aussi télécharger les videos facilement</p> +<p><a href="https://yewtu.be/">https://yewtu.be/</a></p> +<p><a href="https://redirect.invidious.io/">D'autres instances</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Jitsi-vidéoconference.html b/articles/2021/Jitsi-vidéoconference.html new file mode 100755 index 0000000..5ef5b9a --- /dev/null +++ b/articles/2021/Jitsi-vidéoconference.html @@ -0,0 +1,37 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Jitsi vidéoconference</h2> +<h3>2021-04-05</h3> +<p>Il y a un an, on m'a demandé de trouver si notre famille pouvait faire des +rencontres en vidéo pendant que nous étions confinés.</p> + +<p>J'ai testé Jitsi, et j'ai découvert qu'il était idéal +pour les personnes peu familiarisées avec l'informatique, +car il suffit de partager un lien vers le site Web de la réunion Jitsi. +<a href="https://meet.jit.si/" target="_blank">https://meet.jit.si/</a> et d'ajouter un nom unique, par exemple : +<a href="https://meet.jit.si/mon_nom_unique" target="_blank">https://meet.jit.si/mon_nom_unique</a></p> + +<p>Si vous souhaitez organiser une réunion régulière, +réutiliser simplement le même nom à chaque fois, +pensez tout de même à utiliser un nom que personne d'autre ne peut trouver par +hasard.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/La-grande-illusion.html b/articles/2021/La-grande-illusion.html new file mode 100755 index 0000000..cfaaa9a --- /dev/null +++ b/articles/2021/La-grande-illusion.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>La grande illusion</h2> +<h3>2021-05-10</h3> +<p><img src="/images/la-grande-illusion.webp" alt="couverture du livre"></p> +<p>J'ai commencé à lire les notes de Michel Barnier sur les négociations du Brexit. +Il semble être impressionné par les fonctionnaires britanniques, mais beaucoup moins +par les politiciens auxquels ils doivent répondre. C'est très intéressant de connaître les coulisses de l'histoire.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Le-Jardin-de-Tonton-Loïc.html b/articles/2021/Le-Jardin-de-Tonton-Loïc.html new file mode 100755 index 0000000..6a3ac97 --- /dev/null +++ b/articles/2021/Le-Jardin-de-Tonton-Loïc.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Le Jardin de Tonton Loïc</h2> +<h3>2021-04-14</h3> +<p><img src="/images/pyrogravure/graines-tonton-loïc.webp" alt="Le Jardin de +Loïc"></a></p> +<p>Un peu de pyrogravure sur une boîte en bois de chez Amazon pour une +amie.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Lnav---maîtriser-vos-journaux.html b/articles/2021/Lnav---maîtriser-vos-journaux.html new file mode 100755 index 0000000..48367c6 --- /dev/null +++ b/articles/2021/Lnav---maîtriser-vos-journaux.html @@ -0,0 +1,36 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Lnav - maîtriser vos journaux</h2> +<h3>2021-06-13</h3> +<img src="/images/lnav.webp" alt="lnav screenshot"> +<p>Aujourd'hui, j'ai découvert LNAV, un excellent moyen de visualiser les logs de mon serveur.</p> +<p>Sous Centos:</p> +<p> +<pre><code> +sudo dnf install lnav +lnav /var/log +</code></pre> +</p> +<p>Ceci affiche tous les logs dans /var/logs, et en couleur. </p> + +<p>Voir Lnav ici : <a href="https://lnav.org/">lnav.org</a>.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Mbsync-push.html b/articles/2021/Mbsync-push.html new file mode 100755 index 0000000..2d5894b --- /dev/null +++ b/articles/2021/Mbsync-push.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Mbsync push</h2> +<h3>2021-05-04</h3> +<p>J'ai bêtement copié mon .mbsyncrc sur mon nouvel ordinateur portable avant de copier le dossier .mail. Le résultat a été que mbsync a supprimé tout le courrier sur mon serveur +IMAP.</p> +<p>J'ai donc dû tout repousser depuis le dossier .mail de mon ancien ordinateur portable. Heureusement, il y a un bon guide en ligne +<a href="https://aaronweb.net/blog/2014/11/migrating-mail-between-imap-servers-using-mbsync/">ici</a></p> +<p>L'astuce consiste à remettre à zéro le dossier .mail local en supprimant toutes les informations précédentes de mbsync.</p> +</p>retirer toutes les fichiers .mbsyncstate et .uidvalidity:</p> +<code>find . -type f -name '.??*' -delete</code> +<p>Ensuite retirer les identifiants mbsync des noms des fichiers mail:</p> +<code>find . -type f -name '*U=*' | { while read f; do mv "$f" "`echo $f | sed -e 's/,U=[0-9]*//'`"; done }</code> +<p>J'ai ensuite supprimé tous les dossiers sur mon serveur IMAP et j'ai procédé à leur restauration avec un push mbsync.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Mini-chariot-avec-butée-d’arrêt.html b/articles/2021/Mini-chariot-avec-butée-d’arrêt.html new file mode 100755 index 0000000..2d0f9b8 --- /dev/null +++ b/articles/2021/Mini-chariot-avec-butée-d’arrêt.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Mini chariot avec butée d’arrêt</h2> +<h3>2021-12-20</h3> +<p><img src="/images/mini-chariot.webp" alt="image"></p> +<p>Mini chariot avec butée d’arrêt pour scie sous table. Pour empêcher le chariot de dépasser la lame de la scie, j'ai ajouté une petite butée au bout d'un ressort en frêne. La butée descend dans le trou de la plaque de la lame à dégagement +zéro.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Mise-à-jour-sur-la-régulation-du-processeur.html b/articles/2021/Mise-à-jour-sur-la-régulation-du-processeur.html new file mode 100755 index 0000000..54d766e --- /dev/null +++ b/articles/2021/Mise-à-jour-sur-la-régulation-du-processeur.html @@ -0,0 +1,79 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Mise à jour sur la régulation du processeur</h2> +<h3>2021-05-15</h3> +<h3>Old article, I now use tlp,tlp-gui and cpupower</h3> +<p>Mise à jour sur la régulation du processeur pour limiter les problèmes de température</p> +<p>Depuis le <a href="https://wiki.archlinux.org/title/CPU_frequency_scaling#Make_changes_permanent">Arch Linux Wiki</a>:</p> +</p> +<p>Le processeur de mon Thinkpad X220 est un Intel i5-2520M (4) @ 3,200GHz, +je ne dois donc pas utiliser le module acpi-cpufreq, mais le module intel-pstate intégré.</p> +<p><i>Note : Le pilote intel_pstate +ne prend en charge que deux gouverneurs : powersave et performance. +Bien qu'ils partagent le même nom que les gouverneurs génériques, +ils ne fonctionnent pas de la même manière que ces derniers. Les deux +gouverneurs intel_pstate fournissent une mise à l'échelle dynamique +similaire à celle des gouverneurs génériques schedutil ou ondemand. +Le gouverneur de performance fourni par intel_pstate devrait donner une +meilleure fonctionnalité d'économie d'énergie que l'ancien gouverneur à la +demande.</i></p> +</p>J'ai donc installé cpupower en plus de +<a href="2021.html#intel-thermald">thermald</a>:</p> +<code>sudo pacman -S cpupower cpupower-gui +sudo systemctl enable cpupower +sudo systemctl start cpupower +</code> +<p>Pour activer pstate au demarrage j'ai ajouté</p> +<code>GRUB_CMDLINE_LINUX_DEFAULT="intel_pstate=active ...</code> +<p>dans /etc/default/grub et lancé sudo update-grub.</p> +<p>J'ai aussi edité /etc/default/cpupower avec:</p> +<code>governor='performance' +max_freq="3.2GHz" +</code> +<h4>Le script:</h4> +<code>#!/bin/sh +# mettre le gouverneur en mode powersave s'il est déconnecté du secteur. +ACPI=`acpi -a | cut -d ' ' -f 3` +if [ "$ACPI" == "off-line" ] ; then + cpupower frequency-set -g powersave; +else + cpupower frequency-set -g performance ; +fi +</code> +<h4>Créer un regle UDEV</h4> +<code>/etc/udev/rules.d/99-power.rules</code> +<code>ACTION=="change",SUBSYSTEM=="power_supply",RUN+="/usr/bin/bash +/home/philip/.local/bin/power-adapt"</code> +<p>Aussi, lorsque on reboot en batterie, le default est "performance", donc +créer un service systemd pour lancer power-adapt au boot</p> +<code>/etc/systemd/system/power-adapt.service</code> +<code> +[Unit] +Description=your description +[Service] +ExecStart=/home/philip/.local/bin/power-adapt +[Install] +WantedBy=multi-user.target +</code> +<p> et l'installer</p> +<code>sudo systemd enable power-adapt.service</code> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Nerdfonts-in-i3.html b/articles/2021/Nerdfonts-in-i3.html new file mode 100755 index 0000000..5f909c6 --- /dev/null +++ b/articles/2021/Nerdfonts-in-i3.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Nerdfonts in i3</h2> +<h3>2021-05-08</h3> +<p>J'ai utilisé des symboles de police dans i3 et Neomutt, mais j'ai remarqué qu'ils +ont tendance à se planter si vous installez des polices différentes.</p> +</p> +<p>La solution que j'ai utilisée est d'installer une Nerdfont, qui contient tous +les symboles nécessaires en plus du font de base. J'ai téléchargé les polices dejavu +sans mono nerd sur <a href="https://www.nerdfonts.com/">Nerdfonts</a>, et copié +les polices dans /usr/share/fonts/TTF.</p> +<p>J'ai ensuite lancé <pre>fc-cache -f -v</pre> pour mettre à jour le cache des polices.</p> +<p>Pour utiliser cette police dans i3, j'ai modifié la police dans le fichier de configuration de i3 :</p> +<p><code>font pango:DejavuSansMono nerd font mono 11</code></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Nginx-permission-denied.html b/articles/2021/Nginx-permission-denied.html new file mode 100755 index 0000000..de31e0e --- /dev/null +++ b/articles/2021/Nginx-permission-denied.html @@ -0,0 +1,32 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Nginx permission denied</h2> +<h3>2021-04-08</h3> +<p>Ça m'a pris un moment pour comprendre. J'ai généré un rapport à partir de +<a href="https://goaccess.io">goaccess</a>, mais nginx a refusé de l'afficher. +J'ai vérifié les permissions des fichiers mais rien ne clochait. +Il s'avère que c'était SElinux. Je l'ai résolu avec :</p> +<code> +setsebool -P httpd_can_network_connect on<br> +chcon -Rt httpd_sys_content_t /path/to/webroot +</code> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Nouvel-établi-pour-l'atelier.html b/articles/2021/Nouvel-établi-pour-l'atelier.html new file mode 100755 index 0000000..33f9e66 --- /dev/null +++ b/articles/2021/Nouvel-établi-pour-l'atelier.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Nouvel établi pour l'atelier</h2> +<h3>2021-08-29</h3> +<p><img src="/images/new-bench.webp" alt="new bench pic"></p> +<p>Je me suis débarrassé des étagères bon marché que j'utilisais dans le garage, +et je me suis fabriqué un nouveau banc d'atelier de 4 mètres le long du mur avec +du bois de charpente. Plus à venir...</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Odysee.html b/articles/2021/Odysee.html new file mode 100755 index 0000000..f3d4067 --- /dev/null +++ b/articles/2021/Odysee.html @@ -0,0 +1,36 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Odysee</h2> +<h3>2021-04-23</h3> +<p><img src="/images/odysee.webp" alt="Odysee logo"></p> +<p><a href="https://odysee.com/$/invite/EZi5ZxYLVfsjiXdB57SVEmurYt89odSe">Odysee</a> est un site de vidéos basé sur le protocole blockchain LBRY.</p> +<p>Comme <a href="https://peer.tube/">Peertube</a>, il propose un moyen de partager des vidéos sans publicités ni règles +arbitraires destinées à favoriser les annonceurs. +Il n'est pas aussi important que Youtube, et il y a quelques vidéos vraiment bizarres, mais il a de plus en plus d'adeptes et un avenir intéressant.</p> +<p>Quelques-unes de mes chaînes préférées :</p> +<ul> +<li><a href="https://odysee.com/@whatdamath">Whatdamath</a></li> +<li><a href="https://odysee.com/@Linux">Linux Community</a></li> +<li><a href="https://odysee.com/@FreeMovies">Free Movies</a></li> +</ul> +<p>A vous de voir!</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Photo-de-mon-atelier.html b/articles/2021/Photo-de-mon-atelier.html new file mode 100755 index 0000000..2600f5e --- /dev/null +++ b/articles/2021/Photo-de-mon-atelier.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Photo de mon atelier</h2> +<h3>2021-09-03</h3> +<p><img src="/images/workshop-update.webp" alt="workshop update pic"> +<p>Mon petit nirvana. Sur la table, une planche à découper en bois de bout est en train d'être collée. Mon tour fait maison est au bout de la table. Le nouveau banc de 4 mètres rend les choses tellement plus faciles dans un petit espace. Il me manque encore des choses, comme une scie à ruban de 40 cm pour découper des ébauches de bol dans du bois vert, et une raboteuse, mais c'est bien d'avoir des projets quand on est à la retraite +😜.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Planche-à-découper-en-bois-de-bout.html b/articles/2021/Planche-à-découper-en-bois-de-bout.html new file mode 100755 index 0000000..7e08c60 --- /dev/null +++ b/articles/2021/Planche-à-découper-en-bois-de-bout.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Planche à découper en bois de bout</h2> +<h3>2021-09-03</h3> +<p><img src="/images/planche-à-découper-jacqueline.webp" alt="pic"></p> +<p>Planche à couper en chêne avec bois de bout pour plus de résistance à la coupe. Finition huile de noix, 42x24cm</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Project-Hail-Mary.html b/articles/2021/Project-Hail-Mary.html new file mode 100755 index 0000000..4c9c320 --- /dev/null +++ b/articles/2021/Project-Hail-Mary.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Project Hail Mary</h2> +<h3>2021-05-14</h3> +<p><img src="/images/hail-mary-wier.webp" alt="book cover"> +<p>Voici un nouveau livre d'Andy Weir, le célèbre auteur de "The Martian". +Je l'ai placé dans ma liste de lecture, car je n'ai pas encore terminé le livre de Michel +Barnier.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Pyradio.html b/articles/2021/Pyradio.html new file mode 100755 index 0000000..d22a3f3 --- /dev/null +++ b/articles/2021/Pyradio.html @@ -0,0 +1,30 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Pyradio</h2> +<h3>2021-05-25</h3> +<p><img src="/images/pyradio.webp" alt="pyradio screenshot"></p> +<p>Je joue avec Pyradio et Cava en ce moment.</p> +</p>Pyradio est un sympathique lecteur radio pour terminal de <a href="https://github.com/coderholic/pyradio/tree/master">coderholic</a>, et <a href="https://github.com/karlstav/cava">cava</a> est une application simple d'affichage de barres audio, également pour le terminal.</p> +<p>Afin de remplir votre liste de stations radio dans ~/.config/pyradio/stations.csv, +vous pouvez trouver de nombreuses URL ici : <a href="http://listenlive.nl/index.php">Listenlive.nl</a></p> +</p> <p>Je suis donc en train de surfer sur le canapé en écoutant de la Bossa Nova, il y a pire dans la vie.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Scie-à-ruban-fait-maison-(1).html b/articles/2021/Scie-à-ruban-fait-maison-(1).html new file mode 100755 index 0000000..42638a2 --- /dev/null +++ b/articles/2021/Scie-à-ruban-fait-maison-(1).html @@ -0,0 +1,31 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (1)</h2> +<h3>2021-12-10</h3> +<p><img src="/images/bandsaw_1.webp" alt="image"></p> +<p> +Mon nouveau projet est de construire une scie à ruban de 16 pouces à partir des plans de +<a href="https://woodgears.ca/bandsaw/homemade.html">Matthias Wandel</a>. +Ici, j'ai coupé du chêne pour fabriquer le cadre supérieur du support de roue. +</p> +<p><a href="https://woodgears.ca/bandsaw/homemade.html">Woodgears plans</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Scie-à-ruban-fait-maison-(2).html b/articles/2021/Scie-à-ruban-fait-maison-(2).html new file mode 100755 index 0000000..d7139a9 --- /dev/null +++ b/articles/2021/Scie-à-ruban-fait-maison-(2).html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (2)</h2> +<h3>2021-12-16</h3> +<p><img src="/images/support-roue-superior.webp" alt="image"></p> +<p>Support de roue supérieur. J'ai utilisé des chutes de chêne récupérées. Le cadre de tension est renforcé par des +splines dans les coins.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Scie-à-ruban-fait-maison-(3).html b/articles/2021/Scie-à-ruban-fait-maison-(3).html new file mode 100755 index 0000000..b14a22d --- /dev/null +++ b/articles/2021/Scie-à-ruban-fait-maison-(3).html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (3)</h2> +<h3>2021-12-20</h3> +<p><img src="/images/support-trunnion.webp" alt="image"></p> +<p>Poutre support de pivots d'articulation de ta table.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Scie-à-ruban-fait-maison-(4).html b/articles/2021/Scie-à-ruban-fait-maison-(4).html new file mode 100755 index 0000000..fb0aacf --- /dev/null +++ b/articles/2021/Scie-à-ruban-fait-maison-(4).html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (4)</h2> +<h3>2021-12-24</h3> +<p><img src="/images/lower-blade-guide-1.webp" alt="image"></p> +<p> +Guide de la lame inférieure fixé au support du pivot. J'ai légèrement adapté le design de Matthias car je vais utiliser un roulement de 28mm derrière la lame. Le guide est fixé au support du pivot avec un boulon de 8mm dans une douille filetée. Les guides lateraux sont faites en ébene de Gabon. +</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Scie-à-ruban-fait-maison-(5).html b/articles/2021/Scie-à-ruban-fait-maison-(5).html new file mode 100755 index 0000000..53dad31 --- /dev/null +++ b/articles/2021/Scie-à-ruban-fait-maison-(5).html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (5)</h2> +<h3>2021-12-27</h3> +<p><img src="/images/upper-blade-guide-1.webp" alt="image"></p> +<p>Guide de lame supérieur</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Scie-à-ruban-fait-maison-(6).html b/articles/2021/Scie-à-ruban-fait-maison-(6).html new file mode 100755 index 0000000..088d226 --- /dev/null +++ b/articles/2021/Scie-à-ruban-fait-maison-(6).html @@ -0,0 +1,30 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (6)</h2> +<h3>2021-12-31</h3> +<p><img src="/images/trunions-chevilles.webp" alt="image"></p> +<p> +Les supports de pivot sont fixés à la poutre avec des chevilles de 10mm de diamètre et de 90mm de long. Je n'avais pas de chevilles de cette taille, j'ai donc fabriqué un gabarit rapide avec un morceau de cornière, et j'ai poussé des morceaux de chêne a travers le trou avec une perceuse à main. +</p><p> +Ma première tentative avec des chevilles cachées était impossible à aligner correctement, donc je l'ai refait en collant d'abord les supports et puis en perçant directement à travers. +</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Science-et-Vie.html b/articles/2021/Science-et-Vie.html new file mode 100755 index 0000000..75f385a --- /dev/null +++ b/articles/2021/Science-et-Vie.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Science et Vie</h2> +<h3>2021-04-15</h3> +<p>J'ai reçu mon mensuel "Science et vie" aujourd'hui. Je suis un lecteur régulier depuis 40 ans (je me souviens même de l'époque où Antoine de Caunes y écrivait parfois des articles, et où chaque mois avait un schéma électronique à tester). J'aime son objectivité et son large éventail de sujets. Très bon rapport +qualité-prix.</p> +<p><a href="https://www.science-et-vie.com/">Site web Science et Vie</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Sous-plat-avec-incrustation-epoxy-Milliput.html b/articles/2021/Sous-plat-avec-incrustation-epoxy-Milliput.html new file mode 100755 index 0000000..c4e5960 --- /dev/null +++ b/articles/2021/Sous-plat-avec-incrustation-epoxy-Milliput.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Sous-plat avec incrustation epoxy Milliput</h2> +<h3>2021-09-08</h3> +<p><img src="/images/sous-plat-incrustation.webp" alt="pic"></p> +<p>Fait avec des chutes de bois.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/TLS-handshake-failure.html b/articles/2021/TLS-handshake-failure.html new file mode 100755 index 0000000..77d31ec --- /dev/null +++ b/articles/2021/TLS-handshake-failure.html @@ -0,0 +1,31 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>TLS handshake failure</h2> +<h3>2021-04-11</h3> +<p>J'ai continué à obtenir des erreurs de handshake TLS +lorsque jai essayé d'envoyé des emails +après avoir configuré postfix. Après m'être +frappé la tête contre le mur en essayant de trouver une solution, +j'ai finalement réalisé que j'avais supprimé le port 465 sur mon serveur, +mais msmtp avait <code>tls_starttls off</code> dans .msmtprc, qui ne s'applique qu'au port 465. +Problème résolu.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Thinkpad-X200.html b/articles/2021/Thinkpad-X200.html new file mode 100755 index 0000000..70a6aed --- /dev/null +++ b/articles/2021/Thinkpad-X200.html @@ -0,0 +1,31 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Thinkpad X200</h2> +<h3>2021-04-22</h3> +<p><img src="/images/x200.webp" alt="Image Thinkpad X200"></p> +<p>Mon matériel informatique actuel est assez simple. L'époque où je gérais des serveurs à la maison est révolue. +Mon ordinateur portable est un MSI GP60 2QE : résolution d'écran 1380 x 768, RAM 16Gb, 2 disques durs SSD 120Gb et 250Gb, OS Manjaro Linux +XFCE.</p> +<p>C'est une super machine, mais meilleure sur une table. J'aimerais bien avoir un vieux Thinkpad X200 à utiliser +dans un fauteuil. Même 10 ans après leur sortie, ils sont toujours le fantasme des nerds minimalistes avec un écran de 12 pouces et un magnifique clavier mécanique. Solide comme un roc +aussi.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Thinkpad-X220.html b/articles/2021/Thinkpad-X220.html new file mode 100755 index 0000000..2e98c5c --- /dev/null +++ b/articles/2021/Thinkpad-X220.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Thinkpad X220</h2> +<h3>2021-04-27</h3> +<p><img src="/images/Lenovo-ThinkPad-X220.webp" alt="Thinkpad X220 image"></p> +<p>Donc je l'ai fait, je me suis acheté un <b>Thinkpad X220</b> en parfait état avec 8Gb de RAM +et un disque SSD de 240Gb. Cela fait quelques années que je n'ai pas eu deux ordinateurs +à la maison, et maintenant je vais avoir le nirvana des geeks, un Thinkpad fabriqué +avant que Lenovo ne les saborde.</p> +<p>Il y a aussi un grand choix de pièces détachées, vous pourriez en construire une. +J'ai choisi le 12" X220 parce qu'il est destiné à être utilisé sur mes genoux dans un fauteuil, +vous trouverez peut-être que le 14" T420 est un meilleur choix, +il est de la même génération et de la même qualité de construction.</p> +<p>Il y a beaucoup de ressources en ligne pour le X220, par exemple :</p> +<ul> +<li><a href="http://x220.mcdonnelltech.com/resources/">Mcdonnelltech.com</a></li> +<li><a href="http://www.thinkwiki.org/wiki/Category:X220">Thinkwiki</a></li> +</ul> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Télécharger-musique-depuis-Youtube.html b/articles/2021/Télécharger-musique-depuis-Youtube.html new file mode 100755 index 0000000..5f0141b --- /dev/null +++ b/articles/2021/Télécharger-musique-depuis-Youtube.html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Télécharger musique depuis Youtube</h2> +<h3>2021-05-16</h3> +<p><img src="/img/blog/pense-bete.webp" alt="pense-bete"></p> +<p>Installer youtube-dl et ffmpeg, ensuite créer le alias youtube-dl-audio :</p> +<code>alias youtube-dl-audio='youtube-dl --ignore-errors --output "%(title)s.%(ext)s" --extract-audio --audio-format mp3'</code> +<p>Utilisation :</p> +<pre>youtube-dl-audio lien</pre> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Un-espion-dans-ma-poche.html b/articles/2021/Un-espion-dans-ma-poche.html new file mode 100755 index 0000000..94049eb --- /dev/null +++ b/articles/2021/Un-espion-dans-ma-poche.html @@ -0,0 +1,41 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Un espion dans ma poche</h2> +<h3>2021-04-30</h3> +<p><img src="/images/espion-dans-notre-poche.webp" alt="image de téléphone portable"></p> +<p>Nous savons que nos précieux téléphones portables enregistrent presque tout ce que nous leur confions : +fréquentations, déplacements, achats, mots de passe, goûts, etc. +Mais jusqu'où va leur connaissance de notre intimité ? Et à qui transmettent-ils ces informations privées ? +Quel est le niveau de notre surveillance +?</p> +<p>(ré)Voir l'emission +<a href="https://www.france.tv/france-2/envoye-special/2436563-heritage-la-fete-au-village.html">d'Envoyé spécial</a> (avancer à +40mn)</p> +<p>J'ajouterai que mon téléphone portable n'exécute pas de logiciels Google ou Apple pour ces raisons, il exécute plutôt <a +href="https://lineageos.org">Lineageos</a>.</p> +<p> +Tant que je suis sur le sujet de freiner les sociétés GAFA de syphonner mes informations +privées: mon ordinateur portable fonctionne sous <a href="https://manjaro.org/">Manjaro Linux</a>, +et j'utilise le moteur de recherche <a href="https://duckduckgo.com/">Duckduckgo</a> +au lieu de la recherche Google ou Bing.</p> +<p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Une-balancoire-pour-Barbie.html b/articles/2021/Une-balancoire-pour-Barbie.html new file mode 100755 index 0000000..76d9c64 --- /dev/null +++ b/articles/2021/Une-balancoire-pour-Barbie.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Une balancoire pour Barbie</h2> +<h3>2021-12-19</h3> +<p><img src="/images/balancoire.webp" alt="image"></p> +<p>Quelques baguettes pour brochettes, de la colle à bois, et voila.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Update.html b/articles/2021/Update.html new file mode 100755 index 0000000..0697a2a --- /dev/null +++ b/articles/2021/Update.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Update</h2> +<h3>2021-07-25</h3> +<p>Après avoir attrapé le Covid fin mars, j'ai eu des problèmes de dos, d'où la pause silencieuse +ici.</p> + +<p>Cependant, j'ai des projets :</p> + +<p>réorganiser l'espace de travail du garage maintenant que le réservoir de chauffage a été déplacé à l'extérieur, +construire une scie à ruban 16" à partir des plans de Mathias Wandel, +fabriquer un mandrin Longworth pour les bols, +et bien sûr, fabriquer encore plus de bols.</p> + +<p>Surveillez cet espace!</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Wiby.html b/articles/2021/Wiby.html new file mode 100755 index 0000000..f69f10a --- /dev/null +++ b/articles/2021/Wiby.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Wiby</h2> +<h3>2021-04-02</h3> +<p><a href="https://wiby.me/">Wiby</a> est un moteur de recherche web qui ne répertorie que des sites web rapides, simples et sans bavures. Comme avec DuckDuckGo, vous pouvez utiliser les commandes bang pour obtenir des résultats d'autres moteurs de recherche : par exemple, en tapant !g avant votre recherche, vous obtiendrez des données de Google Text Search. Consultez la rubrique "paramètres" pour connaître les autres commandes +disponibles.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/ZRAM.html b/articles/2021/ZRAM.html new file mode 100755 index 0000000..e96afc3 --- /dev/null +++ b/articles/2021/ZRAM.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>ZRAM</h2> +<h3>2021-05-29</h3> +<p>Si vous avez un disque dur SSD, et suffisamment de RAM, c'est une bonne idée d'installer zram car il peut réduire l'écriture sur le SSD.</p> +<p>Le paquet zramswap dans Arch Linux fournit un script automatisé pour mettre en place un swap avec une priorité plus élevée et une taille par défaut de 20% de la taille de la RAM de votre système. Pour le faire automatiquement à chaque démarrage, activez zramswap.service.</p> +<pre>yay -S zramswap +sudo systemctl enable zramswap.service +sudo systemctl start zramswap.service +cat /proc/swaps +</pre> +<p> +<a href="https://wiki.archlinux.org/title/Improving_performance#Zram_or_zswap">Improving +performance</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Zellij-Terminal-Workspace.html b/articles/2021/Zellij-Terminal-Workspace.html new file mode 100755 index 0000000..6815250 --- /dev/null +++ b/articles/2021/Zellij-Terminal-Workspace.html @@ -0,0 +1,30 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Zellij Terminal Workspace</h2> +<h3>2021-04-25</h3> +<p><img src="/images/zellij.webp" alt="zellij image"></p> +<p>Voici un espace de travail terminal prometteur pour les personnes qui aiment les applications basées sur le texte. La version bêta est disponible, ce qui signifie qu'elle fonctionne bien pour un usage quotidien, mais qu'elle peut subir de rares +plantages.</p> +<p>Je le trouve très facile à utiliser. Il occupe maintenant de façon permanente un bureau virtuel dans mon +manjaro/xfce4.</p> +<p>Jetez-y un coup d'oeil ici : <a href="https://zellij.dev/">Zellij</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/erreurs-spamass-milter.html b/articles/2021/erreurs-spamass-milter.html new file mode 100755 index 0000000..3bbc640 --- /dev/null +++ b/articles/2021/erreurs-spamass-milter.html @@ -0,0 +1,43 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>erreurs-spamass-milter</h2> +<h3>2021-05-05</h3> +<p>Je continue à voir de temps en temps /run/spamass-milter/spamass-milter.sock; No such file +or directory dans /var/log/maillog alors que tout semble fonctionner correctement.</p> +<p>Pour le resoudre, je test actuellement:</p> +<ul> +<li>chown sa-milt:postfix /run/spamass-milter</li> +<li>-g postfix dans /etc/sysconfig/spamass-milter EXTRA_FLAGS</li> +<li>chmod 775 /run/spamass-milter</li> +</ul> +<h3>Mise à jour</h3> +<p>Probleme résolu, submission etait en chroot, master.cf est maintenant +comme ceci:</p> +<pre> +# ========================================================================== +# service type private unpriv chroot wakeup maxproc command + args +# (yes) (yes) (no) (never) (100) +# ========================================================================== +smtp inet n - n - - smtpd +submission inet n - n - - smtpd +</pre> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/i3-Blocks.html b/articles/2021/i3-Blocks.html new file mode 100755 index 0000000..058d999 --- /dev/null +++ b/articles/2021/i3-Blocks.html @@ -0,0 +1,50 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>i3 Blocks</h2> +<h3>2021-05-05</h3> +<p>Mon X220 tourne maintenant sous Manjaro i3, et j'ai réussi à obtenir une jolie barre d'état avec +i3-Blocks</p> +<img src="/images/i3-blocks.webp" alt="i3 status image"> +<p>Afin d'obtenir un calendrier pop-up lorsque je clique sur la date dans la barre, j'ai installé +<a href="https://github.com/dmedvinsky/gsimplecal">gsimplecal</a></p> +<img src="/images/gsimplecal.webp" alt="gsimplecal image"> +<p><code>pacman -S gsimplecalendar</code></p> +<p>et modifié .config/I3blocks/config comme suit:</p> +<p><code> +[gsimplecal]<br> +command=date '+%d/%m %H:%M:%S';if [ $button ]; then gsimplecal; fi<br> +interval=1 +</code></p> +<p>Pour faire disparaître le calendrier lorsque le curseur ne le survole plus, +éditez</p> +<p><code> +~/.config/gsimplecal/config +</code></p> +<p>et ajoutez la ligne</p> +<p><code> +close_on_unfocus = 1 +</code></p> +<p>puis redémarrez la barre avec</p> +<p><code> +i3-msg restart +</code></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/spamass-milter-error.html b/articles/2021/spamass-milter-error.html new file mode 100755 index 0000000..8d162e3 --- /dev/null +++ b/articles/2021/spamass-milter-error.html @@ -0,0 +1,56 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>spamass-milter error</h2> +<h3>2021-04-15</h3> +<p>Lorsque j'a regardé si tout allait bien avec spamassasin,</p> +<code>systemctl status spamass-milter</code> +<p>m'affichait le message d'erreur suivant :</p> +<code>spamass-milter could not retrieve sendmail macro "_" !". </code> +<p>Cela n'a aucun effet sur la fonction de spamass_milter, mais je n'aime pas que les choses soient +désordonnées. Il s'avère que spamass-milter a été construit pour sendmail, +donc si vous utilisez Postfix, la macro <b>milter_connect_macro</b> doit être modifiée.</p> +<code>postconf -d milter_connect_macros</code> +<p>renvoie</p> +<code>milter_connect_macros = j {nom_du_démon} {addr_du_démon} v</code> + +<p>Le "_" etait bien manquant, ansi que la macro i.<br> +J'ai donc ajouté cette ligne au fichier /etc/postfix/main.cf</p> + +<code>milter_connect_macros = "i j {daemon_name} v {if_name} _" </code> + +<p>et voila, plus d'erreurs? Non. Une message d'erreur pour le macro "i" subsiste, mais c'est une bogue sans conséquence.</p> + +<p><a href="http://www.postfix.org/MILTER_README.html">http://www.postfix.org/MILTER_README.html</a></p> +<i><pre> +Some Milter applications log a warning that looks like this: + + sid-filter[36540]: WARNING: sendmail symbol 'i' not available + +And they may insert an ugly message header with "unknown-msgid" like this: + + X-SenderID: Sendmail Sender-ID Filter vx.y.z host.example.com <unknown-msgid> + +The problem is that Milter applications expect that the queue ID is known before the MTA accepts the MAIL FROM + (sender) command. Postfix does not choose a queue ID, which is used as the queue file name, until after it + accepts the first valid RCPT TO (recipient) command. +</pre></i> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/web-bloat.html b/articles/2021/web-bloat.html new file mode 100755 index 0000000..b49d673 --- /dev/null +++ b/articles/2021/web-bloat.html @@ -0,0 +1,43 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Le web gonflé</h2> + <h3>03-05-2021</h3> + <p> + Les sites simples, classiques ou en mode texte ont l'avantage de peser beaucoup moins lourd, + de consommer moins de ressources et de CO², et elles satisfont ma nature cartésienne. + </p> + + <h3>La gonflage du web</h3> + <ul> + <li><a href="https://www.webbloatscore.com/">Web Bloat Score</a></li> + <li><a href="https://idlewords.com/talks/website_obesity.htm">Web obesity</a></li> + <li><a href="https://videos.lukesmith.xyz/videos/watch/03bc3b97-b867-4aab-b90c-7cf64ad0b6cf">The war against bloat</a></li> + <li><a href="http://motherfuckingwebsite.com/">Le ... meilleur site web</a></li> + <li><a href="https://alistapart.com/article/sustainable-web-design/">Sustainable web design</a></p> + </ul> + + <h3>Sites web</h3> + <ul> + <li><a href="https://wiby.me/">Wiby - Moteur de recherche des sites classiques</a></li> + <li><a href="https://www.404pagefound.com">404pagefound - Vintage web sites</a></li> + </ul> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/wittr.in.html b/articles/2021/wittr.in.html new file mode 100755 index 0000000..284e7bb --- /dev/null +++ b/articles/2021/wittr.in.html @@ -0,0 +1,33 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>wittr.in</h2> +<h3>2021-05-17</h3> +<p><img src="/images/wttr.webp" alt="image wttr.in"></p> +<p>Plus rapide que Météo France, et sans pistage : wttr.in</p> +<p><a href="https://wttr.in/Saint-Jacut-du-Men%C3%A9?lang=fr">wttr.in</a></p> +<p><a href="http://v2d.wttr.in/Saint-Jacut-du-Men%C3%A9?lang=fr">avec plus de données</a></p> +<p><a href="https://v3.wttr.in/saint-brieuc">vue expérimentale d'une région</a></p> +<p><a href="https://wttr.in/Moon?lang=fr">phases de la lune</a></p> +<p>et c'est encore mieux dans un terminal :</p> +<code>curl wttr.in/Saint-Jacut-du-Mené?lang=fr</code> +<p><a href="https://github.com/chubin/wttr.in">pour plus d'informations</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2021/Étau-de-recup.html b/articles/2021/Étau-de-recup.html new file mode 100755 index 0000000..f8e2dc5 --- /dev/null +++ b/articles/2021/Étau-de-recup.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Étau de recup</h2> +<h3>2021-08-12</h3> +<p><img src="/images/etau.webp" alt="etau"></p> +<p>Voici un étau d'origine soviétique que j'ai trouvé dans une brocante pour 20€.</p> +<p>Un bon bain de vinaigre, une brosse metallique, de la peinture verte, et +du WD40, et voila.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2022/Scie-à-ruban-fait-maison-(10).html b/articles/2022/Scie-à-ruban-fait-maison-(10).html new file mode 100755 index 0000000..b4a9392 --- /dev/null +++ b/articles/2022/Scie-à-ruban-fait-maison-(10).html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (10)</h2> +<h3>2022-02-01</h3> +<p>Le cadre est fait avec 10 planches de sapin du nord, 2000x120x18mm</p> +<p><img src="/images/scie-frame-1.jpg"></p> +<p><img src="/images/scie-frame-2.jpg"></p> +<p><img src="/images/scie-frame-3.jpg"></p> +<p><img src="/images/scie-frame-4.jpg"></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2022/Scie-à-ruban-fait-maison-(11).html b/articles/2022/Scie-à-ruban-fait-maison-(11).html new file mode 100755 index 0000000..6985bd6 --- /dev/null +++ b/articles/2022/Scie-à-ruban-fait-maison-(11).html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (11)</h2> +<h3>2022-02-15</h3> +<p><img src="/images/scie-completed-1.jpg"></p> +<p><img src="/images/scie-completed-2.jpg"></p> +<p><img src="/images/scie-completed-3.jpg"></p> +<p><img src="/images/scie-completed-4.jpg"></p> +<p><img src="/images/scie-completed-5.jpg"></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2022/Scie-à-ruban-fait-maison-(12).html b/articles/2022/Scie-à-ruban-fait-maison-(12).html new file mode 100755 index 0000000..6062f78 --- /dev/null +++ b/articles/2022/Scie-à-ruban-fait-maison-(12).html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (12)</h2> +<h3>2022-02-20</h3> +<h3>Amelioration</h3> +<p>J'ai fabriqué un autre guide lame inférieur qui fonctionne mieux. Celui d'origine présentait la tranche du roulement vers la lame ce qui nécessite un support plus compliqué et moins solide. L'autre avantage d'appuyer la lame sur le côté du roulement est que c'est plus silencieux. Les blocs des guides latéraux vient d'un morceaux d'ébène de Gabon qu'on m'avait donné.</p> +<img src="/images/scie-alignment-2.jpg"> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2022/Scie-à-ruban-fait-maison-(7).html b/articles/2022/Scie-à-ruban-fait-maison-(7).html new file mode 100755 index 0000000..519d319 --- /dev/null +++ b/articles/2022/Scie-à-ruban-fait-maison-(7).html @@ -0,0 +1,30 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (7)</h2> +<h3>2022-01-01</h3> +<p><img src="/images/trunion-shim.webp"></p> +<p> +Après avoir monté les pivots, je me suis rendu compte qu'il leur manquait un millimètre au milieu. Plutôt que de tout démonter, j'ai collé de fines cales en chêne, et je les ai poncées avec du papier de verre collé sur l'autre partie du pivot. +Si jamais je recommence, j'utiliserai une toupie sur un gabarit de compas pour obtenir un ajustement parfait. +</p> +<p><img src="/images/scie-parts-1.jpg"></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2022/Scie-à-ruban-fait-maison-(8).html b/articles/2022/Scie-à-ruban-fait-maison-(8).html new file mode 100755 index 0000000..9160166 --- /dev/null +++ b/articles/2022/Scie-à-ruban-fait-maison-(8).html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (8)</h2> +<h3>2022-01-01</h3> +<p><img src="/images/table-support-trunions.webp"></p> +<p>Support de table montée sur les pivots. La table de la scie à ruban est placée sur le support, ce qui la renforce. Le support est fabriqué avec une chute d'étagère en chêne +laminé.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2022/Scie-à-ruban-fait-maison-(9).html b/articles/2022/Scie-à-ruban-fait-maison-(9).html new file mode 100755 index 0000000..d02e94c --- /dev/null +++ b/articles/2022/Scie-à-ruban-fait-maison-(9).html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Scie à ruban fait maison (9)</h2> +<h3>2022-01-19</h3> +<p><img src="/images/bandsaw-frame-pieces.webp"></p> +<p>Toutes les pieces pour le cadre du scie à ruban découpés. Il m'a fallu 10 planches d'épicéa +2000x120x18mm</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2022/mailcow.html b/articles/2022/mailcow.html new file mode 100755 index 0000000..5b29b53 --- /dev/null +++ b/articles/2022/mailcow.html @@ -0,0 +1,31 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Mailcow</h2> + <h3>24-12-2022</h3> + <img src="/images/mailcow-logo.png"> + <p> + Very happy with Mailcow, it seems to be the best solution currently for a self hosted email server. It allows me to have perfectly authenticated email with DKMS, ARC, SPF and DMARC. I've set it up on an OVH vps and it's been running for a couple of weeks now. + </p><p> + In the past I have maintained by hand email servers, but - been there got the t-shirt. So I tested a few solutions such as Post.Io, iRedmail, etc. but settled on Mailcow as it is the most advanced and complete. Installation took only a few minutes. + </p><p> + <a href="https://mailcow.email/">https://mailcow.email</a> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/1vyrain.html b/articles/2023/1vyrain.html new file mode 100755 index 0000000..e04c7e3 --- /dev/null +++ b/articles/2023/1vyrain.html @@ -0,0 +1,85 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>1vyrain - unlock your Thinkpad T430</h2> + <h3>01-09-2023</h3> + <pre> + d888 d8b +d8888 Y8P + 888 + 888 888 888 888 888 888d888 8888b. 888 88888b. + 888 888 888 888 888 888P" "88b 888 888 "88b + 888 Y88 88P 888 888 888 .d888888 888 888 888 + 888 Y8bd8P Y88b 888 888 888 888 888 888 888 +8888888 Y88P "Y88888 888 "Y888888 888 888 888 + 888 + Y8b d88P + "Y88P" + </pre> + +<p> +The Thinkpad 430 comes with an older wifi card that doesn't support 802.11ac. My machine also didn't come with a Bluetooth card. +</p><p> +In order to fit a replacement card such as the AC 7260, the BIOS must be altered as by default Lenovo only allows you to install hardware they have whitelisted, and as the T430 dates a bit this list is defunct. +</p><p> +Thankfully, 1vyrain has created a BIOS that adds an advanced tab in th BIOS and wipes the whitelist, and allows you to install alternative hardware and deactivate Intel ME among other things. +</p><p> +I followed the following steps: +</p> +<h3>1</h3> +<p> +For this you'll need to be in Windows 64 bit +</p><p> +First downgrade the BIOS to one that is hackable, for the T430 this is version 2.64 +</p><p> +You can either download and install the BIOS from the Official Lenovo page +</p><p> +or use 1vprep: +</p><p> +<a href="https://github.com/n4ru/IVprep">https://github.com/n4ru/IVprep</a> +</p><p> +Click "code"> Download ZIP. Unzip the package and click "downgrade.bat" +</p><p> +The system will reboot and install the BIOS +</p> +<h3>2</h3> +<p> +Boot into the BIOS and make sure Secure boot is disabled, and startup set to Both, UEFI first. +</p><p> +Boot back into Windows +</p><p> +Download the 1vyrain.iso from <a href="https://github.com/n4ru/1vyrain">https://github.com/n4ru/1vyrain</a> +</p><p> +Currently available from <br><a href="https://drive.google.com/open?id=1yusq98ja6NmI4G4txKVueFqY_ZEwaZvO">https://drive.google.com/open?id=1yusq98ja6NmI4G4txKVueFqY_ZEwaZvO</a> +</p><p> +Burn the iso to a USB drive using Rufus <a href="https://rufus.ie">https://rufus.ie</a> (select DD mode) +</p><p> +Reboot from the USB drive and follow the instructions. The computer will go into sleep mode, just hit the power button to get back into 1vyrain +</p><p> +Note: I had to remove the SATA drive where windows was installed to be able to select the USB drive to boot from. +</p><p> +The computer will reboot, it might recycle a few times. It might also display a CRC error, you can ignore this and reboot. +</p><p> +Boot into the BIOS again, you should now see a new tab "Advanced". In this tab you can disable Intel ME. +</p><p> +You are all set! +</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Bitwarden-RBW-blues.html b/articles/2023/Bitwarden-RBW-blues.html new file mode 100755 index 0000000..67d53db --- /dev/null +++ b/articles/2023/Bitwarden-RBW-blues.html @@ -0,0 +1,50 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Bitwarden RBW blues</h2> + <h3>2023-12-05</h3> + <p> + Unfortunately the CLI app for Bitwarden is clunky and difficult to use as + it is stateless. A kind soul wrote an unofficial CLI app called + <a href="https://github.com/doy/rbw">RBW</a> that uses a daemon, but it + stopped working a couple of weeks ago. Issues are pending but no reaction + from the author so far. + </p> + <p>The official Bitwarden CLI app is still working though.</p> + <p> + My current solution is to use password-store on the CLI and Bitwarden in + Firefox and Android. + </p> + <p> + There's a dirty hack called "B" here: + <a href="https://github.com/lebarondecharlus/b" + >https://github.com/lebarondecharlus/b</a + >, but it has the master password in clear in the script. + </p> + <p> + I'm wondering if it's possible to script a bridge between the two, but + it's looking like I need to use password-store for CLI apps, and Bitwarden + for the rest. + </p> + <h3>Update:</h3> + <p>RBW has been fixed. The .eu bitwarden domains have been integrated. Tested in rbw-1.9.0-1 arch package</p> + <p>Check <a href="https://github.com/doy/rbw#configuration">https://github.com/doy/rbw#configuration</a> for usage info.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Chimera's-Star.html b/articles/2023/Chimera's-Star.html new file mode 100755 index 0000000..26d066c --- /dev/null +++ b/articles/2023/Chimera's-Star.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Chimera's Star</h2> +<h3>2023-12-11</h3> +<img src="/images/chimera.jpg"> +<p>With the upcoming release of Chimera's star, I've started reading the whole saga again.</p> +<a href="https://www.glynnstewart.com/universe/starships-mage/">https://www.glynnstewart.com/universe/starships-mage/</a> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Death-of-the-web.html b/articles/2023/Death-of-the-web.html new file mode 100755 index 0000000..ff0462d --- /dev/null +++ b/articles/2023/Death-of-the-web.html @@ -0,0 +1,36 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Death of the web</h2> +<h3>2023-12-07</h3> +<p>I'm wondering where we are going with all this.</p> +<p>Searching today's web I feel like I'm scrabbling around in a trash can for a half eaten sandwich.</p> +<p>Content is there somewhere, but you have to swallow the glitz, blitz, frills, lies, and adverts before you find it.</p> +<p>With the advent of AI written content it can only get much worse, after all, who owns these AI's ? Who's world view will they push?</p> +<p>In a few years time web browsers will be based on the same web engine, +asking if you need some help with that infection you mentioned to your doctor, chiding you for not checking +the local election results, and sugggesting a diet change because of the contents of your last supermarket bill.</p> +<p>Seems like the only way out will require a rupture.<p> +<p>Perhaps take a step back and use older text based browsers with just a smattering of images.</p> +<p>Local BBS's maybe, or Gopher/Gemini servers on everyone's laptop with a decentralized search system.</p> +<p>It's inevitable there will be a backlash, the form it will take should be interesting.</p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Firefox-Multi-Account-Containers.html b/articles/2023/Firefox-Multi-Account-Containers.html new file mode 100755 index 0000000..03423a2 --- /dev/null +++ b/articles/2023/Firefox-Multi-Account-Containers.html @@ -0,0 +1,45 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Firefox Multi Account Containers</h2> + <h3>20-11-2023</h3> + <p> + Firefox Mutli-Account Containers works by isolating your identity into a + separate container that makes it harder to track your visits to other + websites with third-party cookies. + </p> + <p> + For example, you can sign in to your Facebook account inside of a + container, and Facebook will never know who you are whenever you aren’t + inside the container. + </p> + <p> + I use it to contain unsavory web sites like Facebook and Google, Banks and + Shopping sites + </p> + <p> + <a + href="https://addons.mozilla.org/en-US/firefox/addon/multi-account-containers/" + >multi-account-containers</a + > + </p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Forget-VTech,-get-an-old-laptop-instead.html b/articles/2023/Forget-VTech,-get-an-old-laptop-instead.html new file mode 100755 index 0000000..d7d5531 --- /dev/null +++ b/articles/2023/Forget-VTech,-get-an-old-laptop-instead.html @@ -0,0 +1,32 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Forget VTech, get an old laptop instead</h2> +<h3>2023-12-29</h3> +<p>I had the dubious joy of setting up a miserable low powered android excuse for a kids computer from VTech over christmas.</p> +<p>It's so low powered the slow reaction time made me believe it wasn't working for a while. The screen is tiny, the ergonomy crappy, and the software is gahh...</p> +<p>To give the kids something they could actually use without bringing tears to the kids and their parents, I set up an old laptop with Linux Mint Cinnamon.</p> +<p>A nice, easy, and quick installation.</p> +<p>I added Seb's <a href="/articles/2023/protecting-your-computer-with-a-hosts-file.html">IP hosts file</a>, and in Firefox, in addition to the basics (Ublock Origin,Decentraleyes,etc.) I set the search engine and default page to <a href="https://www.qwantjunior.com/">Qwant Junior</a>. I also added some bookmarks like <a href="https://www.webjunior.net/">WebJunior</a>.</p> +<p>With Mint's library of games and educational software, their mum can add more things later as required as only she has the root password.</p> +<p>For the same price of the VTech toy, you can get a refurbished Thinkpad X240 from Backmarket, set it up the same way, and actually have something useable.</p> +<p>UPDATE: The kid loves the old laptop, and the VTech ...thing has been relegated to the dustbin of Christmas past. Win!</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Gemini---The-protocol-not-the-AI.html b/articles/2023/Gemini---The-protocol-not-the-AI.html new file mode 100755 index 0000000..ed725dc --- /dev/null +++ b/articles/2023/Gemini---The-protocol-not-the-AI.html @@ -0,0 +1,60 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Gemini - The protocol not the AI</h2> +<h3>2023-12-09</h3> +<p>Playing with a Gemini server today. I'm not entirely convinced, but in the interest of research, here goes:</p> +<p>First I opened port 1965 on the server</p> +<code>ufw allow 1965 +</code> +<p>I created the user gemini on the server and created the folders bin, certs, and gemini in /var/gemini</p> +<p>I downloaded the x86_64 linux binary from <a href="https://github.com/mbrubeck/agate/releases">github</a> and moved it to /var/gemini/bin.</p> +<p>In /var/gemini/content I creatd the text file index.gmi with just "hello world" in it.</p> +<p>Then I did a first run of Gemini</p> +<code>/var/gemini/bin/agate --content /var/gemini/content/ --certs /var/gemini/certs --hostname wittamore.fr --lang en-GB</code> +<p>Agate Auto created it's required certificates, and using the gemini browser Amfora I saw the "hello world!" text.</p> +<p>So I added a systemd file to manage the execution of Agate:</p> +<code>[Unit] +Description=agate +After=network.target + +[Service] +User=gemini +Type=simple +ExecStart=/var/gemini/bin/agate --content /var/gemini/content/ --certs /var/gemini/certs --hostname wittamore.fr --lang en-GB + +[Install] +WantedBy=default.target +</code> +<p>And launched it</p> +<code>systemctl enable agate.service +systemctl start agate.service +</code> + +<p>Agate is a very simple Gemini server. It automatically creates self-signed certificates for TLS in the .der format, +which makes it clumsy if you have letsencrypt certificates in .pem format available.</p> +<code>openssl rsa -outform der -in privkey.pem -out key.der +openssl x509 -outform der -in cert.pem -out cert.der +</code> +<p>Unlike Gopher, none of the servers available appear to be mature yet, which is probably due to the relatively new protocol.</p> +<p>The <a href="https://admin.flounder.online/">Flounder</a> site shows some interesting possibilities for Gemini.</p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/In-2024,-please-switch-to-Firefox.html b/articles/2023/In-2024,-please-switch-to-Firefox.html new file mode 100755 index 0000000..4b584a3 --- /dev/null +++ b/articles/2023/In-2024,-please-switch-to-Firefox.html @@ -0,0 +1,25 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>In 2024, please switch to Firefox</h2> +<h3>2023-12-29</h3> +<p><a href="https://roytanck.com/2023/12/23/in-2024-please-switch-to-firefox/">In 2024, please switch to Firefox</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Little-Doggies.html b/articles/2023/Little-Doggies.html new file mode 100755 index 0000000..18bdb95 --- /dev/null +++ b/articles/2023/Little-Doggies.html @@ -0,0 +1,39 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Little Doggies</h2> + <h3>2023-12-12</h3> + <p> + If such a thing as gods really existed, they wouldn't let little doggies + suffer and have such short lives. + </p> + <p> + When gods are omnipotent but at the same time vicious, callous, genocidal, + bloodlusting entities, they can only have been made by man, in the image + of man. + </p> + <p> + Bobby was my dog. He had enough character for a pack of dogs, and although + only 7kgs of mostly hair, was the boss, but a boss who melted in my arms whenever + he could. He took an enormous place in our house and hearts, and he had a soul. + Rest in peace Bobby. + </p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Marginalia-search-engine.html b/articles/2023/Marginalia-search-engine.html new file mode 100755 index 0000000..d513d6b --- /dev/null +++ b/articles/2023/Marginalia-search-engine.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Marginalia search engine</h2> +<h3>2023-12-18</h3> +<p>Marginalia is an independent DIY search engine that attempts to show you sites you perhaps weren't aware of.</p> +<p>It does this by up-ranking web sites that have text content, and downranking the typical image heavy, content-poor, advert ridden web sites other search engines will favour. +<p> +<a href="https://search.marginalia.nu/">https://search.marginalia.nu</a> +</p> +<h3>Comments</h3> +<blockquote> +<p>Maginalia is a nice search engine for finding small and obscure sites with good content. Another good one is <a href="http://wiby.me">wiby.me</a></p> +-Mike S. +</blockquote> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Need-a-long-walk.html b/articles/2023/Need-a-long-walk.html new file mode 100755 index 0000000..735529f --- /dev/null +++ b/articles/2023/Need-a-long-walk.html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Need a long walk</h2> +<h3>2023-12-02</h3> +<p> + This time of year I need long walks to clear my brain of the winter grey. The + lack of sunlight and short days isn't good for me. I must have southern + genes somewhere. Roll on March. +</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Web-1.5.html b/articles/2023/Web-1.5.html new file mode 100755 index 0000000..19b0711 --- /dev/null +++ b/articles/2023/Web-1.5.html @@ -0,0 +1,86 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Web 1.5 and Berries</h2> + <h3>2023-12-20</h3> + <p> + I was searching this morning for web 1.0 ressources in French. It was + interesting to see the amount of belittleing phrases, from "passive web" + through "documentary web", "Spectator web", etc. + </p> + <p> + Web 2.0 is referred to as the "social web", where large companies found + how to make money from this thing through centralisation, javascript, + social networks, online sales, and the massive use of <b>berries</b>. The + descriptions of Web 3.0 and 4.0 are so full of bullshit, acronyms, + marketing speak, and futurism, it's not worth commenting on. + </p> + + <p>Berries? you might ask.</p> + + <p> + Human evolution has included colour sight to enable the picking of ripe + fruit and avoidance of dangerous meats or insects. + </p> + <p> + Icons tap into this evolutionary advantage. Images that target our + attention in order to vehicule an emotional response are another. Their + use sometimes has the side effect of manipulating you, the reader. Today + they are also used to track you or vehicule code. + </p> + + <p> + The current web plunges the reader in a (sometimes pretty) morass of + background images, icons, adverts, gadgets, etc. that distract you from + noticing that perhaps there's not much actual content. + </p> + <p> + It also slows web sites dramatically. Try looking at most recipe web sites + for an example. Now visit Luke Smith's + <a href="https://based.cooking/">Based Cooking</a> (He had a rant about + this a while back) + </p> + <p> + Scrolling through Facebook's litany of meme's, repeated jokes, and plain + rubbish, will also make you realise that there's very little content. What + remains, is formatted and presented in an identical fashion, much like + visiting adjacent prison cells. It also dissapears quickly, buried under + more meme's, repeated jokes, and rubbish. And while you are rotting your + brain in this manner, you are being tracked mercilessly while your soul is + sold for a few deniers. + </p> + <p> + In a nutshell, web 1.0 wasn't something to simply discard, and while + technological advances are indiscutable, (UTF-8, CSS, backend + scripting,..) the uses they have been put to sometimes definitely are. + </p> + <p> + I'm no monk: I still need to get to my bank, make the odd online purchase, + etc. but I think it's possible to have in parallel a reasoned web simply + by editing web sites responsably. Not by throwing out the baby with the + bathwater, but eliminating the chaff, reducing the C02 footprint, reducing + the illisibility, making it faster, making it easier for people with + reduced sight to enjoy the content, making sure it can be read in all + browsers, stop tracking in it's... tracks, and getting to the point. + </p> + <p>How about calling it Web 1.5 ?</p> + <p> </p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/Web-site-stats-better-without-Google.html b/articles/2023/Web-site-stats-better-without-Google.html new file mode 100755 index 0000000..02b4167 --- /dev/null +++ b/articles/2023/Web-site-stats-better-without-Google.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Web site stats better without Google</h2> +<h3>2023-12-25</h3> +<p>Merry Christmas!</p> +<p>I used to run this blog on a CMS with all the "required" google schema code included.</p> +<p>Funnily though, since I moved to using my own stripped down html blog with zero search engine optimisation nor shemas, the stats are much better.</p> +<p>Go figure...</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/ascii-art.html b/articles/2023/ascii-art.html new file mode 100755 index 0000000..5c0dcfc --- /dev/null +++ b/articles/2023/ascii-art.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Ascii Art</h2> + <h3>27-11-2023</h3> +<pre> + ____ _ _ +| __ ) | | ___ __ _ __ _ (_) _ __ __ _ ___ +| _ \ | | / _ \ / _` | / _` || || '_ \ / _` |/ __| +| |_) || || (_) || (_| || (_| || || | | || (_| |\__ \ +|____/ |_| \___/ \__, | \__, ||_||_| |_| \__, ||___/ + |___/ |___/ |___/ +</pre> +<p> Old as the hills, and still amusing...</p> +<ul> +<li><a href="https://www.asciiart.eu/">https://www.asciiart.eu/</a></li> +<li><a href="https://ascii.co.uk/https://ascii.co.uk/">https://ascii.co.uk/</a></li> +</ul> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/connerie-des-dates-limites.html b/articles/2023/connerie-des-dates-limites.html new file mode 100755 index 0000000..d92efd5 --- /dev/null +++ b/articles/2023/connerie-des-dates-limites.html @@ -0,0 +1,25 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>La connerie des dates limites</h2> + <h3>20-08-2023</h3> + <img src="/images/dates-limites.jpg"> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/debian-or-arch.html b/articles/2023/debian-or-arch.html new file mode 100755 index 0000000..185768a --- /dev/null +++ b/articles/2023/debian-or-arch.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Debian 12 or Arch</h2> + <h3>03-09-2023</h3> + <img src="/images/debian-vs-arch.jpg"> + <p> + I installed Debian 12 on my T430, as that's what I use on my vps servers, but after using Arch Linux on my X220 I'm a bit dubious. + <p></p> + For example, Firefox. The stable release of Debian has a much older version, if you want to use the latest version you either have to get the unstable packages via the pin system or use a snap package. + <p></p> + Using pin with an unstable package messes up your installation because afterwards I needed to install curl, and guess what, it has to be unstable too otherwise you can't install it because of modified dependancies. + <p></p> + For Neomutt, a 2023 version isn't even in unstable, so the only solution is to download a ton of development dependancies and compile it myself. Been there done that but it leads me to doubt the system. Maybe there will be tears down the road with some other unforseen problem. + <p></p> + Conclusion, Debian is the best for servers, but maybe less so for my Laptop. + <p></p> + Back to Arch then. + </p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/drm-et-firefox.html b/articles/2023/drm-et-firefox.html new file mode 100755 index 0000000..9e56430 --- /dev/null +++ b/articles/2023/drm-et-firefox.html @@ -0,0 +1,39 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>DRM dans Firefox</h2> + <h3>03-09-2023</h3> + <p> + Ma Freebox m'empêche de regarder sans problème les vidéos d'Amazon Prime. Les chaînes de télévision normales fonctionnent bien, mais Amazon Video ne cesse de se couper. + </p><p> + Sur ma smartphone ou sur ordinateur pas de problem. + </p><p> + Je regarde donc actuellement Amazon Prime Video sur mon ordinateur portable avec Firefox. + </p><p> + Il suffit d'activer le contenu DRM dans les paramètres : + </p> + <img src="/images/firefox-drm.jpg"> + <p> + Et la, ça marche plutôt bien. + <p></p> + Je pense que le logiciel du boîtier TV de Free doit passer par la fenêtre pour obtenir une mise à jour. + </p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/evil-empire.html b/articles/2023/evil-empire.html new file mode 100755 index 0000000..eff8071 --- /dev/null +++ b/articles/2023/evil-empire.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Evil Empire</h2> + <h3>10-11-2023</h3> + <img src="/images/evil-empire.jpg" alt="evil empire"> + <p>So facebook has thrown down the sword. Pay up or bend over.</p> + <p>Thanks but no thanks.</p> + <p> + There are many other solutions that respect your privacy, you don't need + to use stealth slot machines like Facebook or X rated Twitter. + </p> + <p> + <a href="https://joinmastodon.org/fr">Check out Mastodon for example</a> + </p> + <p> + <h3>Update</h3> + Maybe not going the way Meta wants, see: <a href="https://wittamore.fr/articles/2023/noyb-files-gdpr-comp.html">noyb files GDPR complaint<a> + </p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/fixing-25-second-firefox-delay.html b/articles/2023/fixing-25-second-firefox-delay.html new file mode 100755 index 0000000..13edab4 --- /dev/null +++ b/articles/2023/fixing-25-second-firefox-delay.html @@ -0,0 +1,45 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + + <main> +<article> +<h2>Fixing a 25 second startup delay in Firefox</h2> + <h3>06-09-2023</h3> + <p> + I found Firefox was taking a while to launch in XFCE4. I tested removing + the plugins, emptying the cache, etc. but nothing worked. + </p> + <p> + Eventually I ran "strace Firefox" and found it was hanging polling + something for 25 seconds. + </p> + <p> + So armed with the readout from strace, I searched and found the solution : + </p> + <code> +sudo pacman -Rndd xdg-desktop-portal +</code> + <p> + I kept xdg-desktop-portal-gtk as the bug isn't in there. Apparently + xdg-desktop-portal-gnome has the same bug, but I don't use Gnome so... + </p> + <p>Both Firefox and Libreoffice now launch in milliseconds!</p> + <p>I hope this helps someone else...</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/hardening-firefox.html b/articles/2023/hardening-firefox.html new file mode 100755 index 0000000..67ea172 --- /dev/null +++ b/articles/2023/hardening-firefox.html @@ -0,0 +1,47 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Hardening Firefox</h2> + <h3>19-11-2023</h3> + <p> + I don't like spyware disguised as web browsers, so I don't use Google + Chrome, Microsoft Edge, Opera, etc. + </p> + <p> + Instead I use Firefox with a hardened user.js , which is the configuration + file for Firefox settings + </p> + <p> + <a href="https://github.com/arkenfox/user.js">arkenfox/user.js</a> + </p> + <p>and the following add-ons:</p> + <ul> + <li>Ublock Origin</li> + <li>Privacy Badger</li> + <li>Decentraleyes</li> + </ul> + <p> + I also use a private search engine, + <a href="https://docs.searxng.org/https://docs.searxng.org/">SearXNG</a> + to avoid tracking and profiling. + </p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/images-in-xterm-emulators.html b/articles/2023/images-in-xterm-emulators.html new file mode 100755 index 0000000..4066ad7 --- /dev/null +++ b/articles/2023/images-in-xterm-emulators.html @@ -0,0 +1,45 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Images in terminal emulators</h2> + <h3>13-11-2023</h3> + <img src="/images/xterm-images-400.jpg" alt="xterm-images"> + <p> + Playing with newsboat and W3M as pager, I've been checking Terminals that support W3M images without flickering when scrolling, or other display artifacts. Unfortunately I've only found one, Xterm. + </p><p> + There are a lot of publications on the web proposing arcane hacks, might not work for you's, unmaintained stuff, etc. but in reality there's no cut and dried solution for W3M's own image rendering that works on all terminal emulators. + </p> + <ul> + <li>Xterm - Images display correctly</li> + <li>Kconsole - Images display but bar across the screen at the cursor position</li> + <li>Xfce4-terminal - Images display only when scrolling with flicker</li> + <li>Alacritty - Images display only when scrolling with flicker</li> + <li>Mate Terminal - Images display only when scrolling with flicker .etc. etc.</li> + <li>Kitty - has it's own image routines, but I was unable to get it to work with W3M 's kitty display option</li> + </ul> + <p> + The working solution I found was sixel support, see <a href="https://www.arewesixelyet.com/">arewesixelyet.com</a> + </p><p> + Today the winner is: Alacritty with W3M's img2sixel option + </p> + <img src="/images/w3m-display-options.png" alt="w3m-options"> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/jersey-before.html b/articles/2023/jersey-before.html new file mode 100755 index 0000000..7becb1c --- /dev/null +++ b/articles/2023/jersey-before.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Jersey before 541 a.d.</h2> + <h3>24-10-2023</h3> + <img src="/images/jersey-before.jpg"> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/kind-of-blue.html b/articles/2023/kind-of-blue.html new file mode 100755 index 0000000..83c1f7b --- /dev/null +++ b/articles/2023/kind-of-blue.html @@ -0,0 +1,52 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + + <main> + <article> + <h2>Kind of Blue</h2> + <h3>11-11-2023</h3> + <p> + The best selling Jazz album of all time. 1959, the year everything + changed. + </p> + <div class="wrapper"> + <iframe + src="https://www.youtube.com/embed/j9QXpfvgSVk" + title="Miles Davis - Kind of Blue - Full Album" + frameborder="0" + allow=" + accelerometer; + autoplay; + clipboard-write; + encrypted-media; + gyroscope; + picture-in-picture; + web-share; + " + allowfullscreen + ></iframe> + </div> + <p> + <a href="https://youtu.be/j9QXpfvgSVk" + >https://youtu.be/j9QXpfvgSVk</a + > + </p> + </article> + </main> + <footer> + <p> </p> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2023/linux-paradise.html b/articles/2023/linux-paradise.html new file mode 100755 index 0000000..7f18aee --- /dev/null +++ b/articles/2023/linux-paradise.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Linux paradise</h2> + <h3>15-08-2023</h3> + <img src="/images/linux-servers-cloud.jpg"> + <p>Dad, what are clouds made of?</p> + <p>Linux servers, mostly</p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/map-of-langourla.html b/articles/2023/map-of-langourla.html new file mode 100755 index 0000000..6830f76 --- /dev/null +++ b/articles/2023/map-of-langourla.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>A map of Langourla</h2> + <h3>24-08-2023</h3> + <img src="/images/langourla_map.jpg"> + <p> + A few years ago I made this map of Langourla using Inkscape. + </p><p> + Notably it shows the old roman roads and an estimation of the chemin de St.Gilles from some of the documents found at the BNF. + </p><p> + A friend asked me for it last week, so I thought I'd post it here. + </p> + <p> + <a href="/files/langourla.pdf">langourla.pdf</a> + </p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/mer-des-faluns.html b/articles/2023/mer-des-faluns.html new file mode 100755 index 0000000..8425c2b --- /dev/null +++ b/articles/2023/mer-des-faluns.html @@ -0,0 +1,30 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Mer des Faluns</h2> + <h3>11-11-2023</h3> + <img src="/images/mer-des-faluns.jpg" alt="mer-des-faluns"> + <p> + Quand la Bretagne était une île + </p> + <a href="https://fr.wikipedia.org/wiki/Mer_des_Faluns">Wikipaedia</a> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/micro-text-editor.html b/articles/2023/micro-text-editor.html new file mode 100755 index 0000000..1be9026 --- /dev/null +++ b/articles/2023/micro-text-editor.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2> + Micro text editor</h2> + <h3>28-08-2023</h3> + <p> + I've been using Joe as my go-to shell text editor for years, writing web sites with it, and basically all text writing. Recently I've been looking at Micro as it has a Prettier plugin. This plugin uses Prettier to format your code upon saving your file. Micro is vastly superior to Nano and has a number of useful plugins. + </p><p> + I have never been able to be productive with Vim. + </p><p> + I'm so set in my ways regarding Joe that I'll have to alias joe to micro in order to use it, but I think it's the way forward. + </p><p> + <a href="https://micro-editor.github.io/">https://micro-editor.github.io</a> + </p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/minimalist-blog.html b/articles/2023/minimalist-blog.html new file mode 100755 index 0000000..714e790 --- /dev/null +++ b/articles/2023/minimalist-blog.html @@ -0,0 +1,87 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>A Minimalist blog</h2> + <h3>23-11-2023</h3> + <h6>Updated 07-09-2025</h6> + <p> + In the past I've used several blog engines, CMS, and rolled my own solutions, but at + the end of the day, I find writing simple html is faster and easier than using a blog + engine. A blog engine or CMS makes you dependant on other softwareand may no longer exist + or be obsolete in a couple of years. Plus you usually have to hack the html and css + of these engines to get what you want anyway, which defeats their purpose. + </p> + <p> + After playing with 8 bit computers, my IT career debuted writing invoicing + applications using Multiplan on MS-DOS, so plain text has a nostalgic pull + for me. Searching for a reasonable solution, I looked at + <a href="https://en.wikipedia.org/wiki/Gopher_(protocol)">Gopher</a> and + <a href="https://en.wikipedia.org/wiki/Gemini_(protocol)">Gemini</a>. + </p> + <p> + Both are nice although Gopher is old and quirky and Gemini still a bit experimental, + but the fundemental problem is that they both require specific browsers, html bridges or plugins to be viewed, + limiting their use and content availability to a few initiates, and I <i>really</i> mean a few. + Lets face it, the www is the common denominator today. + </p> + <p> + Ok, you can't put the djinn back in the bottle. You can do a load of stupid stuff + with a web site, and there are billions of them out there producing carbon, wasting energy, + and displaying a marked lack of content behind a show of technological sleight of hand. + </p> + <p> + Instead, the onus is on <b>you</b> to write a universally readable web site, + not on <b>the reader</b> to posess the right browser. + </p> + <p> + Of course, you can't force people to be responsible, and major search engines + don't offer to filter out crap websites. In the mean time, I'll practice what I preach. + </p> + <p> + So here's a blog with articles that just uses simple html. + There are no Javascript, grids, or + fancy formats. Articles are plain html and can be read in a terminal text browser like + <a href="https://lynx.browser.org/">Lynx</a>, or a simple browser + graphical browser like <a href="https://dillo.org/">Dillo</a>, or a state + of the art browser like + <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a>. + </p> + <p> + The only extras are the use of some basic css for browsers that can use + it, and UTF-8 character encoding rather than ASCII. Behind the scenes + there's some PHP for generating article listings and search, and a couple of + bash scripts to edit new articles and upload them. I think + this arrangement will be pretty future resistant.</p> + <p><a href="https://git.wittamore.fr/blog/about/">You can download the scripts here</a> + </p> + <p> + A Google only web is on the horizon. Let them have it. Instead let's go + back to enjoying a fast, pertinent, content-rich space where you are not + the product. + </p> + <p> + For info, I use <a href="https://librewolf.net">Librewolf</a>, + a more secure version of Firefox, and + <a href="https://sr.ht/~bptato/chawan/">Chawan</a>, + perhaps the best terminal web browser to date. + </p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/ms-eu-fine.html b/articles/2023/ms-eu-fine.html new file mode 100755 index 0000000..e3c7656 --- /dev/null +++ b/articles/2023/ms-eu-fine.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Another EU fine coming in 5..4..3..</h2> + <h3>30-08-2023</h3> + <p> + Microsoft is using malware-like pop-ups in Windows 11 to get people to + ditch Google + </p> + <p> + <a + href="https://www.theverge.com/2023/8/30/23851902/microsoft-bing-popups-windows-11-malware" + >https://www.theverge.com/2023/8/30/23851902/microsoft-bing-popups-windows-11-malware</a + > + </p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/new-television.html b/articles/2023/new-television.html new file mode 100755 index 0000000..6d9ff57 --- /dev/null +++ b/articles/2023/new-television.html @@ -0,0 +1,41 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + +<main> +<article> +<h2>New Television</h2> + +<h3>05-10-2023</h3> +<img src="/images/tcl.webp"> +<p> +My old 43" Panasonic bought in 2007 for 2500 was a bit tired, or was it me tired of it? +</p><p> +Whatever the reason, I decided to change it. After visiting the local shops and getting asked questions like "what is a bluetooth remote?" by salespeople, I ended up buying online. +</p><p> +If people turn to buying online, traditional shops are largely to blame in my humbler opinion. +</p><p> +The TCL 43C641 arrived, the first thing I noticed was it's weight, so much lighter than the old Panasonic. I bought a separate central column stand as I don't like the diddly feet that require a support larger than the television to not fall off. +</p><p> +Setup was a breeze, although android TV boxes go out of their way to stop you from running apps upon launch. For example my provider is Free, and so I use the OQEE app instead of the Free TV box, but impossible to have it automatically run when the telly starts. +</p><p> +The strangest thing is the definition. We were so used to the Panasonic, the higher definition somehow makes films or series look as if they were shot by an amateur. I guess it's habit. +</p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/newsboat-strip-junk-from-web-pages.html b/articles/2023/newsboat-strip-junk-from-web-pages.html new file mode 100755 index 0000000..d3ea26f --- /dev/null +++ b/articles/2023/newsboat-strip-junk-from-web-pages.html @@ -0,0 +1,46 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Newsboat: Strip junk from webpages</h2> +<h3>17-11-2023</h3> + <p> + Install Python readability + </p> + <code>yay -S python-readability-lxml</code> + <p> + Test output + </p> + <code> + python3 \ -m readability.readability -u \ 'https://www.servethehome.com/amd-psb-vendor-locks-epyc-cpus-for-enhanced-security-at-a-cost/' \ 2> /dev/null \ 1> output.html + </code> + <p> + In ~/.newsboat/config + </p><p> + set macro - french keyboard comma semicolon is practical + </p> + <code> + macro ; set browser "python3 -m readability.readability -u %u 2> /dev/null | w3m -T text/html"; open-in-browser ; set browser "w3m %u" + </code> + <p> + <a href="https://youtu.be/i4ndCJDvBbo">Source</a> + </p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/noyb-files-gdpr-comp.html b/articles/2023/noyb-files-gdpr-comp.html new file mode 100755 index 0000000..5c9d131 --- /dev/null +++ b/articles/2023/noyb-files-gdpr-comp.html @@ -0,0 +1,42 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>GDPR complaint against Meta</h2> +<h3>28-11-2023</h3> +<p> + Today, noyb filed a complaint against Meta with the Austrian data protection + authority. European users now have the “choice” to either consent to being + tracked for personalized advertising – or pay up to €251.88 a year to retain + their fundamental right to data protection on Instagram and Facebook. Not only + is the cost unacceptable, but industry numbers suggest that only 3 percent of + people want to be tracked – while more than 99 percent decide against a + payment when faced with a “privacy fee”. If Meta gets away with this, + competitors will soon follow in its footsteps. Given that the average phone + has 35 apps installed, keeping your phone private could soon cost around € + 8,815 a year. +</p> +<p> + <a + href="https://noyb.eu/en/noyb-files-gdpr-complaint-against-meta-over-pay-or-okay" + >https://noyb.eu</a + > +</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/nuclear-sabotage.html b/articles/2023/nuclear-sabotage.html new file mode 100755 index 0000000..3ec563a --- /dev/null +++ b/articles/2023/nuclear-sabotage.html @@ -0,0 +1,47 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + + <main> + <article> + <h2>Nuclear Sabotage</h2> + <h3>14-09-2023</h3> + <p> + How Russia incited German and French politicians to sabotage French + nuclear power. It nearly worked. + </p> + <div class="wrapper"> + <iframe + src="https://www.youtube.com/embed/lnUpNQU8Loc" + title="EP28: Comment l'Allemagne et la Russie (et certains français) affaiblissent le nucléaire français" + frameborder="0" + allow=" + accelerometer; + autoplay; + clipboard-write; + encrypted-media; + gyroscope; + picture-in-picture; + web-share; + " + allowfullscreen + ></iframe> + </div> + </article> + </main> + <footer> + <p> </p> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2023/obscurantism.html b/articles/2023/obscurantism.html new file mode 100755 index 0000000..e91c9ba --- /dev/null +++ b/articles/2023/obscurantism.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Obscurantism</h2> + <h3>17-10-2023</h3> + <img src="/images/obscurantism.jpg"> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/pacman-yay-notes.html b/articles/2023/pacman-yay-notes.html new file mode 100755 index 0000000..f846500 --- /dev/null +++ b/articles/2023/pacman-yay-notes.html @@ -0,0 +1,33 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Arch Linux Pacman Notes</h2> + <h3>01-01-2023</h3> + <p> + Clean unused packages: pacman -Qdtq | pacman -Rs - + </p><p> + Clean package cache: pacman -Sc + </p><p> + Purge package: pacman -Runs + </p><p> + yay -Scc + </p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/password-store-remote.html b/articles/2023/password-store-remote.html new file mode 100755 index 0000000..867c01b --- /dev/null +++ b/articles/2023/password-store-remote.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Password-store remote server</h2> + <h3>02-09-2023</h3> + <p> + I added a remote server for pass so I can share the passwords on my new-to-me laptop. + </p><p> + see: <a href="https://gist.github.com/abtrout/d64fb11ad6f9f49fa32">https://gist.github.com/abtrout/d64fb11ad6f9f49fa32</a> + </p><p> + and don't forget to + </p> + <ul> + <li>Add the Passff extension to Firefox</li> + <li>Add the host app</li> + </ul> + <code> + curl -sSL github.com/passff/passff-host/releases/latest/download/install_host_app.sh | bash -s -- firefox + </code> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/password-store-to-bitwarden.html b/articles/2023/password-store-to-bitwarden.html new file mode 100755 index 0000000..8fd66a0 --- /dev/null +++ b/articles/2023/password-store-to-bitwarden.html @@ -0,0 +1,79 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>password-store to bitwarden</h2> +<h3>2023-12-04</h3> +<h4>Migrating from password-store to Bitwarden and RBW</h4> +<p> + I've been using Pass (<a href="https://www.passwordstore.org/" + >password-store</a + >) for a couple of years now, and it works really well on the command line and + with command line apps. Browser extensions exist that while not earth + shattering, do work. Setting up a remote git repository and sharing the + repository on another computer is relatively easy, and overall it appeals to + my ideal of no-nonsense software. +</p> +<p> + Problems arise however when you want to share your password store on Android. + Getting the android app to work with the same repository is a few steps away + from planetary orbit insertion calculations. +</p> +<p> + After fiddling with it for a few hours, I threw in the towel. I must be too + dumb. The most recent user written howto I found dated from 2018, for an older + version. I get the impression that nobody really uses it. With probable + reason. +</p> +<p> + So with a sigh in my heart I've opted to move to + <a href="https://bitwarden.com/">Bitwarden</a> for now. +</p> +<h4>Bitwarden</h4> +<p> + Bitwarden is free for personal use, but also has some extras with $10 and $40 + year plans. +</p> +<p> + Bitwarden's browser extensions are great, Android support is great, and + there's a command line app. You can even set up your own Bitwarden repository, + although that's something I don't really need. +</p> +<p> + Bitwarden doesn't have LastPass's data breach history, and it appears to cater + to people like me who just want the job done without a ton of + commercialisation effects. +</p> +<h4>Migration</h4> +<p> + There are a couple of ways to migrate from Pass to Bitwarden, but I found that + Pass can pile up a lot of obsolete and irregular data, so I've installed the Bitwarden and + Pass Browser Firefox extensions side by side, so I can build the Bitwarden vault as I go + along. +</p> +<h3>Update</h3> +<p>I've been using Bitwarden for two years now and I'm completely satisfied.</p> +<p>Bitwarden's unofficial command line application <a href="https://github.com/doy/rbw">RBW</a> +has come a long way. It's now easy to include RBW in the mbsync config file .mbsyncrc for +example to obtain a stored password with</p> +<code>Passcmd "rbw get [folder/password]"</code> +<p>I use the same synchronised vault on Android and two different laptops.</p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/periodic-fstrim-on-ssd-drives.html b/articles/2023/periodic-fstrim-on-ssd-drives.html new file mode 100755 index 0000000..5e22772 --- /dev/null +++ b/articles/2023/periodic-fstrim-on-ssd-drives.html @@ -0,0 +1,40 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Periodic fstrim on SSD drives</h2> + <h3>05-11-2023</h3> +<p> +The util-linux package provides fstrim.service and fstrim.timer systemd unit files. +</p><p> +Enabling the timer will activate the service weekly. The service executes fstrim(8) on all mounted filesystems on devices that support the discard operation. +</p><p> +The timer relies on the timestamp of /var/lib/systemd/timers/stamp-fstrim.timer (which it will create upon first invocation) to know whether a week has elapsed since it last ran. +</p><p> +Therefore there is no need to worry about too frequent invocations, in an anacron-like fashion. systemctl enable fstrim.timer +</p><p> +To query the units activity and status, see journalctl. +</p><p> +To change the periodicity of the timer or the command run, edit the provided unit files. +</p> +<a href="https://wiki.archlinux.org/title/Solid_state_drive">https://wiki.archlinux.org/title/Solid_state_drive</a> + + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/prettier-when-it-works.html b/articles/2023/prettier-when-it-works.html new file mode 100755 index 0000000..8e0c196 --- /dev/null +++ b/articles/2023/prettier-when-it-works.html @@ -0,0 +1,50 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + <hr /> + </header> + + <main> +<article> +<h2>Prettier when it works</h2> + <h3>24-08-2023</h3> + <img src="/images/prettier.jpg" /> + <p>Prettier is a great code beautifier for PHP, once you get it to work.</p> + + <h3>Update 14 may 2025</h3> + <p>For Archlinux:</p> + <code>pacman -S prettier +yay -S prettier-plugin-php</code> + <p>edit ~/.prettierrc</p> + <pre> +{ + "plugins": ["/usr/lib/node_modules/@prettier/plugin-php/src/index.mjs"] +} + </pre> + <p>In order to use it in the Micro text editor, download micro-prettier:</p> + <pre> + git clone https://github.com/claromes/micro-prettier.git + cd micro-prettier + make + </pre> + <p>If you would rather not have it running on every save, in micro:<br> + Ctrl+e > set prettier.onsave false</p> + </article> +</main> +<footer> + + <p> </p> + <hr /> + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2023/protecting-your-computer-with-a-hosts-file.html b/articles/2023/protecting-your-computer-with-a-hosts-file.html new file mode 100755 index 0000000..8ec8edf --- /dev/null +++ b/articles/2023/protecting-your-computer-with-a-hosts-file.html @@ -0,0 +1,42 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Protecting your computer with a hosts file</h2> + <h3>19-11-2023</h3> + <p><a href="https://sebsauvage.net/wiki/doku.php?id=accueil">Sebsauvage</a> has put together a great hosts file: + <a href="https://sebsauvage.net/wiki/doku.php?id=dns-blocklist-en">dns-blocklist</a> + </p> + <p> + DNS filtering is a simple way of blocking the majority of ads, trackers and malicious sites with minimum effort. Set-up is fast and easily reversible. + </p> + <h3>On Linux</h3> +<code>sudo curl "https://sebsauvage.net/wiki/doku.php?do=export_code&id=dns-blocklist&codeblock=0" -o /etc/cron.weekly/hosts-update +sudo chmod +x /etc/cron.weekly/hosts-update</code> + <p> + Run the first update: + </p> +<code>sudo /etc/cron.weekly/hosts-update</code> + <h3>On Windows</h3> + <p> + Windows networking is a steaming pile of manure, but you can still install this file with caveats. + </p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/quad-9.html b/articles/2023/quad-9.html new file mode 100755 index 0000000..1987f9d --- /dev/null +++ b/articles/2023/quad-9.html @@ -0,0 +1,30 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + +<main> +<article> +<h2>Quad9 and systemd-resolved</h2> +<h3>05-09-2023</h3> +<p> +I set up systemd-resolved with the Quad9 DNS servers, with Cloudflare as backup. Quad9 is a Swiss DNS service that blocks malicious hosts and improves performance. +</p> +<a href="https://geekflare.com/linux-server-local-dns-caching/">https://geekflare.com/linux-server-local-dns-caching</a> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/radio-jazz-soul.html b/articles/2023/radio-jazz-soul.html new file mode 100755 index 0000000..1c2ff6d --- /dev/null +++ b/articles/2023/radio-jazz-soul.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Radio Jazz & Soul</h2> +<h3>28-11-2023</h3> + +<a href="https://www.radio.fr/s/jazzradio">Sur radio.fr</a> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/saint-catherine.html b/articles/2023/saint-catherine.html new file mode 100755 index 0000000..c34e17a --- /dev/null +++ b/articles/2023/saint-catherine.html @@ -0,0 +1,37 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Saint Catherine</h2> + <h3>17-11-2023</h3> + <img src="/images/saint-catherine.jpg" alt="saint catherine"> + <p> + <i>À la Sainte-Catherine, tout bois prend racine</i> + </p><p> + On Saint Catherine's Day ( 25th November), all wood takes root + </p><p> + I haven't suddenly converted to religion, it's just a saying... + </p><p> + When the plants are not using energy to produce leaves, but the weather is mild and wet, is an ideal time for cuttings. + </p><p> + The sun is out so I snipped and planted a load of cuttings, some nicked from the local church garden. + </p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/searxng-a-private-search-engine.html b/articles/2023/searxng-a-private-search-engine.html new file mode 100755 index 0000000..bd6fbb7 --- /dev/null +++ b/articles/2023/searxng-a-private-search-engine.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Searxng - a private search engine</h2> + <h3>12-11-2023</h3> + <p> + I've installed a copy of Searxng. + </p><p> + It's also on my laptop, but my internet connection is so crappy it's faster to have it on a vps + </p><p> + Searxng is a free meta-search engine, meaning that it does not manage the search itself but relies on search engines such as bing, google, duckduckgo, etc. that it uses anonymously. + </p><p> + The difference is that nothing is tracked, nobody is profiling you, there are no logs, thus your searches are totally private. + </p> + <a href="https://docs.searxng.org/index.php">https://docs.searxng.org/index.php</a> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/searxng-on-my-laptop.html b/articles/2023/searxng-on-my-laptop.html new file mode 100755 index 0000000..f566d65 --- /dev/null +++ b/articles/2023/searxng-on-my-laptop.html @@ -0,0 +1,59 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>SearxNG on my laptop</h2> + <h3>05-11-2023</h3> + <p>To keep my searches private, I've installed SearxNG on the T430.</p> + <p>Here's how I did it: My OS is Arch linux.</p> + <pre> +$ cd ~/Downloads +$ git clone https://github.com/searxng/searxng.git searxng +$ cd searxng sudo -H ./utils/searxng.sh install all +</pre + > + <p> + I use Nginx as a web server on my laptop, so I added the following in + /etc/nginx/sites-available/localhost + </p> + <pre> +location /searxng { + uwsgi_pass unix:///usr/local/searxng/run/socket; + include uwsgi_params; + uwsgi_param HTTP_HOST $host; + uwsgi_param HTTP_CONNECTION $http_connection; + + # see flaskfix.py + uwsgi_param HTTP_X_SCHEME $scheme; + uwsgi_param HTTP_X_SCRIPT_NAME /searxng; + # see limiter.py + uwsgi_param HTTP_X_REAL_IP $remote_addr; + uwsgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for; +} +</pre + > + <p>Navigate to localhost/searxng and there's our search engine.</p> + <p> + In order to use it as the default search engine in Firefox, right click on + the url and select searxng as a search engine. + </p> + <p>Then in settings set searxng as the default search engine.</p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/system-collapse.html b/articles/2023/system-collapse.html new file mode 100755 index 0000000..846523f --- /dev/null +++ b/articles/2023/system-collapse.html @@ -0,0 +1,33 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>System Collapse</h2> + <h3>14-11-2023</h3> + <img src="/images/systemcollapse.jpg" alt="book cover"> + <p> + I have this in my Kindle as a next read... + </p> + <p><b>Update:</b>Not as good as the previous books, Murderbot gets closer to figuring out humans, but no real surprises.</p> + <p> + <a href="https://www.amazon.fr/System-Collapse-Murderbot-Diaries-English-ebook/dp/B0BQGJHG3Q">Amazon link</a> + </p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/t430-cpu-upgrade.html b/articles/2023/t430-cpu-upgrade.html new file mode 100755 index 0000000..5cda990 --- /dev/null +++ b/articles/2023/t430-cpu-upgrade.html @@ -0,0 +1,41 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Thinkpad T430 CPU Upgrade</h2> + <h3>27-10-2023</h3> + <img src="/images/t430-cpu-upgrade.jpg"> +<p> +I've updated the CPU of my Thinkpad T430 +</p><p> +The laptop came with a dual core i5 , but as it is a highly upgradable machine, I've added a quad core i7–3632QM +</p><p> +I also changed the cooler to increase the amount of copper acting as the heatsink. +</p><p> +The benchmark shows a nice improvement and everything feels snappier. Probably unnecessary for basic use, but I plan to do some DVD transcoding, so in all a good move. See: +<a +href="https://medium.com/@n4ru/the-definitive-t430-modding-guide-3dff3f6a8e2e">The Definitive T430 Modding +Guide</a> +</p><p> +<h4>Update</h4> +<p> +The standard 65 watt charger that comes with the T430 no longer charges while the laptop is running as the new CPU sucks more juice that the charger can handle, so I've bought a 90 watt charger. The battery itself runs for about a bit less than before due to the same cause. It's looking good ! +</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/t430-cpu-upgrade.html~ b/articles/2023/t430-cpu-upgrade.html~ new file mode 100755 index 0000000..9cb1c56 --- /dev/null +++ b/articles/2023/t430-cpu-upgrade.html~ @@ -0,0 +1,38 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + <!-- article start --> + <h2>Thinkpad T430 CPU Upgrade</h2> + <h5>27-10-2023</h5> + <img src="/images/t430-cpu-upgrade.jpg"> +<p> +I've updated the CPU of my Thinkpad T430 +</p><p> +The laptop came with a dual core i5 , but as it is a highly upgradable machine, I've added a quad core i7–3632QM +</p><p> +I also changed the cooler to increase the amount of copper acting as the heatsink. +</p><p> +The benchmark shows a nice improvement and everything feels snappier. Probably unnecessary for basic use, but I plan to do some DVD transcoding, so in all a good move. See: The Definitive T430 Modding Guide +</p><p> +<h4>Update</h4> +<p> +The standard 65 watt charger that comes with the T430 no longer charges while the laptop is running as the new CPU sucks more juice that the charger can handle, so I've bought a 90 watt charger. The battery itself runs for about a bit less than before due to the same cause. It's looking good ! +</p> + <!-- article end --> + + <div class="center"> + A minimalist blog by <a href="mailto:%70%68%69%6C%69%70%40%77%69%74%74%61%6D%6F%72%65%2E%63%6F%6D">Philip Wittamore</a> + </div> + </body> +</html> diff --git a/articles/2023/thinkpad-t430.html b/articles/2023/thinkpad-t430.html new file mode 100755 index 0000000..75390fe --- /dev/null +++ b/articles/2023/thinkpad-t430.html @@ -0,0 +1,48 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Thinkpad T430</h2> + <h3>27-08-2023</h3> + <p> + Since april 2021 I've been using a Thinkpad X220. I upgraded it with 16Gb ram and a 240Gb SSD hard drive. Really happy with it, solid well built no-nonsense laptop. + </p><p> + But I have a growing problem, the 12" 1366 x 768 is a bit small for developing web sites, and my eyes aren't what they used to be. + </p><p> + So I've bought another old Thinkpad, this time a T430. It has a 14" 1600 x 900 screen. + </p><p> + Upgrades: 16Gb ram, a 128Gb msata hard disk, a 480Gb SSD hard disk, and AC-7260HMW wifi card. + </p><p> + I'll upgrade the Bios with 1vyrain to unlock the limits of Ivybridge laptops and install the AC-7260. + </p><p> + It's also possible to upgrade the screen and processor. + </p><p> + People tend to fall in love with these laptops, because their build and upgradablilty have never been bettered, + and where else can you get a Thinklight? + </p><p> + (Framework laptops look nice, but an older Thinkpad is waaaay cheaper!) + </p><p> + A few links about this machine: + </p><p> + <a href="https://www.thinkwiki.org/wiki/Category:T430">https://www.thinkwiki.org/wiki/Category:T430</a> + </p><p> + <a href="https://medium.com/@n4ru/the-definitive-t430-modding-guide-3dff3f6a8e2e">https://medium.com/@n4ru/the-definitive-t430-modding-guide-3dff3f6a8e2e</a> + </p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/updating-my-thinkpad-t430-wifi-card.html b/articles/2023/updating-my-thinkpad-t430-wifi-card.html new file mode 100755 index 0000000..8864181 --- /dev/null +++ b/articles/2023/updating-my-thinkpad-t430-wifi-card.html @@ -0,0 +1,67 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + +<main> +<article> +<h2>Updating my Thinkpad T430 wifi card</h2> +<h3>06-09-2023</h3> +<img src="/images/ac-7620-spot.jpg"> +<p> +The wifi card that was installed in my laptop was an Intel® Centrino® Advanced-N 6205 +</p><p> +The N 6205 is a dual band card but unlike the AC 7620, It doesn't have an integrated bluetooth card, nor 802.11ac +</p><p> +I previously flashed the bios with 1vyrain see article, which wipes the whitelist that would otherwise prevent me from installing hardware not on the list. So I can now install the AC 7620. +</p><p> +The T430 doesn't activate this card unless the 51st pin is removed. I removed the pin with my Dremel (some use tape). +</p><p> +This pin is the last pin on the right when the card is face up. I put the laptop back together and checked. +</p><p> +The wifi came on automatically. +</p><p> +I then installed Bluez: +</p><code> +sudo pacman -S bluez sudo pacman -S bluez-utils sudo pacman -S blueman +sudo micro /etc/bluetooth/main.conf set AutoEnable=true +sudo systemctl enable bluetooth.service +sudo systemctl start bluetooth.service +</code> +<p> +I made sure pulseaudio was installed: +</p> +<code> +sudo pacman -S pulseaudio +sudo pacman -S pulseaudio-bluetooth +sudo pacman -S pavucontrol +sudo systemctl enable pulseaudio +sudo systemctl start pulseaudio +</code> +<p> +Then I connected my bluetooth headset and ... it just worked +</p><p> +Sources: +</p><p> +<a href="https://medium.com/@n4ru/the-definitive-t430-modding-guide-3dff3f6a8e2e">https://medium.com/@n4ru/the-definitive-t430-modding-guide-3dff3f6a8e2e</a> +<br> +<a href="https://www.jeremymorgan.com/tutorials/linux/how-to-bluetooth-arch-linux/">https://www.jeremymorgan.com/tutorials/linux/how-to-bluetooth-arch-linux/</a> +<br> +<a href="https://www.sevarg.net/2022/12/03/thinkpad-t430s-wireless-combo-card-upgrade/">https://www.sevarg.net/2022/12/03/thinkpad-t430s-wireless-combo-card-upgrade/</a> +<br> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/windowmaker-0.96.html b/articles/2023/windowmaker-0.96.html new file mode 100755 index 0000000..fb1b0a5 --- /dev/null +++ b/articles/2023/windowmaker-0.96.html @@ -0,0 +1,48 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Window maker 0.96</h2> + <h3>06-11-2023</h3> + <img src="/images/windowmaker096.png"> + <p> + Windowmaker has had it's first update since 2000, so I had to try it out. + </p> + <p> + Windowmaker was written in 1997. For me it's a blast from the past as I + used to run it in a commercial setting, dual booting with windows 3.11 and + running Applixware 4.41 with custom software I wrote for labeling luxury + goods. + </p> + <p> + I chose Windowmaker as it was easy for my collegues to select an app with + a nice big button. + </p> + <p> + After using FVWM, it was easy to set up in comparaison and a unique + paradigm straight from Steve Job's Nextstep. I loved it. + </p> + <p> + Nowdays I use mainly the Xfce4 environment, but it's nice to have + something else to play with. I've used Tiling window managers such as i3 + but they just don't work for me. + </p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2023/windowmaker-in-1998.html b/articles/2023/windowmaker-in-1998.html new file mode 100755 index 0000000..d1ea148 --- /dev/null +++ b/articles/2023/windowmaker-in-1998.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + + <main> +<article> +<h2>Windowmaker in 1998</h2> + <h3>07-11-2023</h3> + <p> + I've kept a couple of screenshots of an application I wrote using + Applixware 4.41, running on Windowmaker, in 1998. + </p> + <p> + It was used with an early HP laser printer with refillable toner pots to + produce personalised product labels that had a lot of variables. + </p> + <img src="/images/applixware-labelmaker_0.jpg"> + <img src="/images/applixware-labelmaker_1.jpg"> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2024/A-new-lathe.html b/articles/2024/A-new-lathe.html new file mode 100755 index 0000000..5bf9069 --- /dev/null +++ b/articles/2024/A-new-lathe.html @@ -0,0 +1,32 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>New lathe</h2> +<h3>2024-02-22</h3> +<img src="/images/coronet-herald.jpg"> +<p>I haven't updated this blog for a couple of months due to my yearly winter brain fog during the dark season, +but here I am again. I've just ordered a new lathe from Bordet, the Coronet Herald.</p> +<p>My home-made lathe works but has limitations that I hope to overcome, +and it's going to give me a well needed push to get back into making things rather than being a couch potato.</p> +<p>I chose the Coronet Herald as it is the only lathe in it's category that has a rotating headstock, +which enables turning of large pieces, and in general it is at the top of all reviews.</p> +<p>I also have a project for a new workshop, as sharing the garage with madame is far from optimal and a guy needs his man-cave, right?</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2024/Another-Oland-tool.html b/articles/2024/Another-Oland-tool.html new file mode 100755 index 0000000..419369b --- /dev/null +++ b/articles/2024/Another-Oland-tool.html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Another Oland tool</h2> +<h3>2024-03-18</h3> +<img src="/images/new-oland-tool.jpg"> +<p>I'm making myself some new oland tools. The ones I have <a href="/static/oland-tools.html">are rather big</a>, and I wanted some finer tools.<p> +<p>This one is for 5mm HSS tool bits. It has a 7mm hole in a 12mm steel shaft with a set screw to hold the tool bit, +which can be ground to any form</p> +<p>#woodturning #tools #homemade</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2024/support-percé-pour-bougie-chauffe-plat.html b/articles/2024/support-percé-pour-bougie-chauffe-plat.html new file mode 100755 index 0000000..6895073 --- /dev/null +++ b/articles/2024/support-percé-pour-bougie-chauffe-plat.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Support percé pour bougie chauffe-plat</h2> +<h3>2024-03-11</h3> +<p><img src="/images/tournages/porte-bougie-1.jpg"></p> +<p>I was give a scrap block of Iroko to try out, and came up with this.</p> +<p>Piercing the holes took a while even with mini drill. It has a Tung oil finish.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/A-fledgling-bash-function-library.html b/articles/2025/A-fledgling-bash-function-library.html new file mode 100755 index 0000000..d59d1d0 --- /dev/null +++ b/articles/2025/A-fledgling-bash-function-library.html @@ -0,0 +1,33 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>A fledgling bash function library</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>A fledgling bash function library</h2> +<h3>2025-05-16</h3> +<h4>Messing with bash functions</h4> +<p>I needed a function for writing colour text, so I thought why not start a library +and learn some bash scripting? Anyway, it's up on my git repository.</p> +<p><a href="https://git.wittamore.fr/bashlib/about/">Bashlib git repository</a></p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/AC7260-wifi-card-on-Thinkpad-X220.html b/articles/2025/AC7260-wifi-card-on-Thinkpad-X220.html new file mode 100755 index 0000000..0d6475a --- /dev/null +++ b/articles/2025/AC7260-wifi-card-on-Thinkpad-X220.html @@ -0,0 +1,36 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>AC7260 wifi card on Thinkpad X220</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>AC7260 wifi/bluetooth card on Thinkpad X220</h2> +<h3>2025-04-05</h3> +<h4>Easy card replacement</h4> +<img width="20%" src="/images/ac-7620-spot.png" alt="ac7260 card"> +<p>I had previously <a href="https://www.bios-mods.com/forum/showthread.php?pid=141098#pid141098">upgraded the bios</a> +on this machine so I could whitelist future hardware.</p> +<p>The new card was easy to install: remove the keyboard by unscrewing 2 screws under the RAM trapdoor</p> +<p>Carefully remove the keyboard by sliding it towards the screen to uncover the lower tabs and unplug it, +then remove the screws holding the palmrest and remove the palmrest carefully unplugging it.</p> +<p>The wifi card socket is on the right next to the M2 ssd socket</p> +<p>I removed pin 51 on the AC7620 with a dremel rather than using tape. Pin 51 is used to activate or deactivate certain card functions, +including Bluetooth, and can be problematic on certain thinkpads. I did the same for my T430.</p> +<p>See also <a href="/articles/2025/X220-internal-Bluetooth-controller.html">Deactivating the X220 internal Bluetooth controller</a></p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Amazon-playing-silly-buggers.html b/articles/2025/Amazon-playing-silly-buggers.html new file mode 100755 index 0000000..a4ca86f --- /dev/null +++ b/articles/2025/Amazon-playing-silly-buggers.html @@ -0,0 +1,39 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Amazon playing silly buggers</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Amazon playing silly buggers</h2> +<h3>2025-03-21</h3> +<h4>I've dropped my Kindle for a Kobo</h4> +<p><img src="/images/kobo-clara-colour.png"></p> +<p>I downloaded all my purchased books, around 500, before Amazon terminated the possibility. Even though it says "purchase" on their site in reality they are now selling you a license, and only allowing books to be read on Kindles.<p> +<p>Enough of this.</p> +<p>Using Calibre I created a library that can be read on any ebook reader, and purchased a Kobo Clara colour.<p> +<p>Now I'm as happy as a pig in muck. Oh and Amazon, I won't be buying books from you any more. There's more than enough alternative sources.</p> +<p>** I love Glynn Stewarts science fiction stories, but most of his work is +published on Amazon. The solution was simple, I subscribed to Glynn's Patreon account and +receive advanced copies of his books that I can read on any reader.</p> +<p><a href="https://www.patreon.com/glynnstewart">https://www.patreon.com/glynnstewart</a></p> +<p>That said, I've read nearly all of Michael Connelly's books but some of +his ebooks on Google or Kobo are country restricted. For example I' unable to purchase the +full series of the Jack Macevoy series because I'm in France. WTF? All this +does is tell me to get a book from another author. Get a clue guys!</p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Anacron-on-Artix.html b/articles/2025/Anacron-on-Artix.html new file mode 100755 index 0000000..624a53a --- /dev/null +++ b/articles/2025/Anacron-on-Artix.html @@ -0,0 +1,93 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Anacron on Artix</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Anacron on Artix</h2> +<h3>2025-06-16</h3> +<h4>On Arch Anacron is a systemd process</h4> +<p>Anacron ensures cron scripts are run on machines that are frequently powered off</p> +<p>On Artix it can be run via cron (cronie)</p> + +<h3>The /etc/crontab file</h3> + +<code># /etc/crontab: configuration file for cron +# See cron(8) and crontab(5) for details. +# m h dom mon dow user command + +SHELL=/bin/bash +PATH=/sbin:/bin:/usr/sbin:/usr/bin +MAILTO=root +HOME=/ + +# run-parts +# (also runs the 0anacron script every hour) +12 * * * * root run-parts /etc/cron.hourly +22 10 * * * root run-parts /etc/cron.daily +32 10 * * 0 root run-parts /etc/cron.weekly +42 10 1 * * root run-parts /etc/cron.monthly +</code> + +<h3>The /etc/cron.hourly/0anacron script</h3> + +<code>#!/bin/sh +# Check whether 0anacron was run today already +if test -r /var/spool/anacron/cron.daily; then +day=`cat /var/spool/anacron/cron.daily` +fi +if [ `date +%Y%m%d` = "$day" ]; then +exit 0; +fi + +# Do not run jobs when on battery power +if test -x /usr/bin/on_ac_power; then +/usr/bin/on_ac_power >/dev/null 2>&1 +if test $? -eq 1; then +exit 0 +fi +fi +/usr/sbin/anacron -s +</code> + +<h3>The /etc/anacrontab file</h3> + +<code># /etc/anacrontab: configuration file for anacron + +# See anacron(8) and anacrontab(5) for details. + +SHELL=/bin/sh +PATH=/sbin:/bin:/usr/sbin:/usr/bin +MAILTO=root +# the maximal random delay added to the base delay of the jobs +RANDOM_DELAY=45 +# the jobs will be started during the following hours only +START_HOURS_RANGE=3-22 + +#period in days delay in minutes job-identifier command +1 5 cron.daily nice run-parts /etc/cron.daily +7 25 cron.weekly nice run-parts /etc/cron.weekly +@monthly 45 cron.monthly nice run-parts /etc/cron.monthly +</code> + + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Annoying-touchpad.html b/articles/2025/Annoying-touchpad.html new file mode 100755 index 0000000..e4f784c --- /dev/null +++ b/articles/2025/Annoying-touchpad.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Annoying touchpad</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Annoying touchpad</h2> +<h3>2025-04-28</h3> +<h4>My palm keeps hitting the trackpad</h4> +<p>So I deactivated it. Got a red touchpoint for that!</p> +<pre> +sudo pacman -S xf86-input-synaptics +synclient TouchpadOff=1 +</pre> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Archwiki-on-my-laptop.html b/articles/2025/Archwiki-on-my-laptop.html new file mode 100755 index 0000000..bb91166 --- /dev/null +++ b/articles/2025/Archwiki-on-my-laptop.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Archwiki on my laptop</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Archwiki on my laptop</h2> +<h3>2025-05-15</h3> +<h4>Wikiman enables you to peruse wiki's on your computer</h4> +<pre> +pacman -S wikiman +pacman -S arch-wiki-docs +cp /etc/wikiman.conf ~/.config/wikiman/ +</pre> +<p>edit ~/.config/wikiman/wikiman.config to your liking. I use the chawan browser instead of w3m for example.</p> +<p><a href="https://github.com/filiparag/wikiman">git page for Wikiman</a></p> +<p>Note: wikiman can be used in the TTY which can be a life saver if you have borked your installation.</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Back-to-the-future.html b/articles/2025/Back-to-the-future.html new file mode 100755 index 0000000..54908c1 --- /dev/null +++ b/articles/2025/Back-to-the-future.html @@ -0,0 +1,37 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Back to the future</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Back to the future</h2> +<h3>2025-04-25</h3> +<h4>4 years afterwards</h4> +<p>Back in <a href="https://wittamore.fr/articles/2021/Pyradio.html">2001</a> I was using Pyradio with Cava on Manjaro with i3</p> +<p>Today I'm using the same apps on Arch Linux with DWM</p> +<p>Not quite sure if I'm going backwards or forwards...</p> +<img src="/images/blog/pyradio-again.png" alt="pyradio screenshot"> +<h3>Pyradio-m3u</h3> +<p>Here's a handy app that fills pyradio with a few thousand stations</p> +<p><a href="https://www.rahuljuliato.com/posts/pyradio-m3u">pyradio-m3u</a></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Bash-prompt-without-scripts.html b/articles/2025/Bash-prompt-without-scripts.html new file mode 100755 index 0000000..cf6c42c --- /dev/null +++ b/articles/2025/Bash-prompt-without-scripts.html @@ -0,0 +1,37 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bash prompt without scripts</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Bash prompt without scripts</h2> +<h3>2025-05-06</h3> +<h4>A pretty prompt in one line of my .bashrc</h4> +<p>I tried several bash prompt scripts but returned to just having a single line in my .bashrc</p> +<pre> +export PS1='\[\e[30;102m\] \A \[\e[92;103m\]\[\e[30m\] \u@\h \[\e[93;107m\]\[\e[30m\] \w \[\e[0;97m\]\n\[\e[0m\]\$ ' +</pre> +<p><a href="https://bash-prompt-generator.org/">This site is a great generator</a></p> +<p>I used the base 16 colour palette so that it ends up displayed using the Pywal16 terminal palette.</p> +<p><img src="/images/blog/bash-prompt-pywal.png"></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Betterfox.html b/articles/2025/Betterfox.html new file mode 100755 index 0000000..cf9985c --- /dev/null +++ b/articles/2025/Betterfox.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Betterfox</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Betterfox</h2> +<h3>2025-03-22</h3> +<h4>With all the hoohaa around Firefox wanting your data I started using Floorp instead.</h4> +<p>But there is hope. Betterfox makes your Firefox safer and faster by modifying user.js</p> +<p>There's a lot of super interesting information on the site too.</p> +<p><a href="https://github.com/yokoffing/BetterFox">BetterFox</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Blog-scripts.html b/articles/2025/Blog-scripts.html new file mode 100755 index 0000000..9c418a8 --- /dev/null +++ b/articles/2025/Blog-scripts.html @@ -0,0 +1,44 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Blog scripts</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Blog scripts</h2> +<h3>2025-05-13</h3> +<h4>My simple blog scripts</h4> +<p>I've added my blog scripts to the git server just in case someone +needs a quick simple blog like this one.</p> +<p>Blog articles are simply html, the site structure is on your computer +and there are a couple of Bash scripts to manage it:</p> +<ul> +<li><b>blogthis</b> builds an articles header and footer, opens the editor</li> +<li><b>blogsend</b> uses ssh to upload all recently added or modified files</li> +<li><b>Blogrss</b> builds/updates the rss feed</li> +<li><b>Blogsitemap</b> builds/updates the sitemap</li> +</ul> +<p>Note that these Bash scripts rely on your having set up ssh correctly to allow +you to ssh to your web server without entering a password.</p> +<p>The articles are listed by three php files that include a search form.</p> +<p><a href="https://git.wittamore.fr/blog/about/">get the files here</a></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Bringing-back-the-fun.html b/articles/2025/Bringing-back-the-fun.html new file mode 100755 index 0000000..5cd95de --- /dev/null +++ b/articles/2025/Bringing-back-the-fun.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bringing back the fun</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Bringing back the fun</h2> +<h3>2025-04-23</h3> +<h4>I installed xscreensaver and it's cool</h4> +<p>Many years ago my window managers were FVWM and Windowmaker, and they were a great deal of fun.</p> +<p>Installing DWM is a breath of fresh air, giving me a bit of nostalgia for the good old days.<p> +<p>I'm trying to not fall into the trap of patching DWM to death and adding useless bling, just keeping it simple.</p> +<p>However, today I installed xscreensaver and my screen lit up reminding me that like the indie web, older stuff can be fun.</p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Browser-choices.html b/articles/2025/Browser-choices.html new file mode 100755 index 0000000..d8b483a --- /dev/null +++ b/articles/2025/Browser-choices.html @@ -0,0 +1,45 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Browser choices</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Browser choices</h2> +<h3>2025-04-02</h3> +<h4>Browser, browser, on the wall, who is the fairest of them all?</h4> +<p>Here's my totally not objective shortlist to date</p> +<ul> +<li>Firefox - doing stupid things for money</li> +<li>Floorp - so many options I wound up not using</li> +<li><b>Chawan - works great with Ghostty, Neomutt & Newsboat</b></li> +<li>W3M - best of the text browsers after Chawan</li> +<li>Lynx - borks on ipv6</li> +<li>Dillo - fast and simple, too simple?</li> +<li><b>Librewolf - a lean secure Firefox</b></li> +<li>Waterfox - yeah but Librefox is leaner</li> +<li>Mullvad - nice and secure, I don't need a VPN though, yet</li> +<li>Otter - nice but crashed</li> +<li>Qutebrowser - yet another barrel of keystrokes to learn</li> +<li>Nyx - yet another barrel of keystrokes to learn</li> +<li>Chrome - google's squirmy tentacles, no thanks</li> +<li>Chromium - I just use it for Jitsi</li> +<li>Brave - crypto what?</li> +<li>Vivaldi - ok but it didn't hook me</li> +<li>Edge - vade retro satana</li> +</ul> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Carbide-tools-for-wood-turning.html b/articles/2025/Carbide-tools-for-wood-turning.html new file mode 100755 index 0000000..09ea597 --- /dev/null +++ b/articles/2025/Carbide-tools-for-wood-turning.html @@ -0,0 +1,45 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Carbide tools for wood turning</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Carbide tools for wood turning</h2> +<h3>2025-09-07</h3> +<h4>Making my own tools</h4> +<p><img src="/images/blog/carbide-tools.jpg" alt="image"></p> +<p>I obtained some HRC61~63 steel rods and made my own carbide wood turning tools.</p> +<p>As the rods are 12mm with a round profile instead of square, presentation of the carbide cutters at various angles, not just 90° is enabled. +My thinking was that I needed some beefy handles to provide leverage and facilitate control..</p> +<p>The handles are made of Ash, and came from the winter woodpile. A more noble end than being thrown in the woodburner.</p> +<p>First impressions are good, specially the round cutter. It looks like these tools are shapers, and position themselves between +the roughing gouge and finition.</p> +<p>Note that you'll need a hss+cobalt drill bit to make a hole in the hardened steel shafts.</p> +<p>Shafts: <a href="https://www.amazon.fr/-/en/Linear-Shaft-Horizontal-Precision-Bearing/dp/B0D2KZXJ2K">Linear bearing shafts, 12mm x 600mm</a></p> +<p>Carbide inserts:<br><a href="https://www.amazon.fr/-/en/dp/B09NKT95TX">15mm round</a><br> +<a href="https://www.amazon.fr/-/en/dp/B09K7JB21W?th=1">15mm square</a></p> +<p>I might get some diamond shaped carbide cutters too, but I have some home made hss tools that do roughly the same job.</p> +<p>See <a href="/articles/2024/Another-Oland-tool.html">here</a> and <a href="https://wittamore.fr/sticky/woodworking/oland-tools.html">here</a> +for more of my home made lathe tools</p> +<p>#woodturning #tools #homemade</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Chaotic-Aur.html b/articles/2025/Chaotic-Aur.html new file mode 100755 index 0000000..44e7ad1 --- /dev/null +++ b/articles/2025/Chaotic-Aur.html @@ -0,0 +1,43 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Chaotic Aur</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Chaotic Aur</h2> +<h3>2025-03-23</h3> +<h4>Chaotic Aur is an automated building repo for AUR packages</h4> +<p>Retrieve the primary key to enable the installation of our keyring and mirror list</p> +<code>sudo pacman-key --recv-key 3056513887B78AEB --keyserver keyserver.ubuntu.com +sudo pacman-key --lsign-key 3056513887B78AEB +</code> +<p>Install the chaotic-keyring and chaotic-mirrorlist packages</p> +<code>sudo pacman -U 'https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst' +sudo pacman -U 'https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst' +</code> +<p>Append (adding at the end) the following to /etc/pacman.conf</p> +<code>[chaotic-aur] +Include = /etc/pacman.d/chaotic-mirrorlist +</code> + +<p>Run a full system update along syncing the mirrorlist</p> +<code>sudo pacman -Syu</code> + +<p>The repository is ready to use! 🚀</p> +<code>sudo pacman -S firedragon</code> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Closing-laptop-lid-without-suspend.html b/articles/2025/Closing-laptop-lid-without-suspend.html new file mode 100755 index 0000000..5890d25 --- /dev/null +++ b/articles/2025/Closing-laptop-lid-without-suspend.html @@ -0,0 +1,44 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Closing laptop lid without suspend</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Closing laptop lid without suspend</h2> +<h3>2025-07-04</h3> +<h4>Looking into using a laptop as a home server</h4> +<p>Since I've got a fibre connection I've been musing about not paying for a VPS and using a home server instead.</p> +<p>I have an unused laptop that could fit the bill maybe. The advantage of a laptop is that 1) I have one and 2) No need for +a ups or screen.</p> +<p>I'm not really sure that I'll build a home server like this, but I'm collecting info.</p> +<p>One of the things I was wondering is how to stop a laptop from entering suspend when the lid is closed. I found a +comment on unixstackexchange that explains how to do it on a non-systemd installation. (and a systemd one too but who cares about that?)</p> +<p>Find the path of the lid switch:</p> +<code>cat /proc/acpi/wakeup</code> +<p>Find the line for the lid and enter the command to deactivate it, for example:</p> +<code>echo 'PNP0C0D:00' | sudo tee /sys/bus/acpi/drivers/button/unbind</code> +<p>If it works, you can add the above line to /etc/rc.local to make it permanent.</p> +<p><a href="https://unix.stackexchange.com/questions/706471/linux-cli-disable-sleep-when-laptop-lid-closed">Original comment</a></p> +<p>On systemd installations, edit /etc/systemd/logind.conf and uncomment the following line: HandleLidSwitch=ignore.</p> + + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Cpu-scaling-governors-on-Thinkpax-X220.html b/articles/2025/Cpu-scaling-governors-on-Thinkpax-X220.html new file mode 100755 index 0000000..72ce067 --- /dev/null +++ b/articles/2025/Cpu-scaling-governors-on-Thinkpax-X220.html @@ -0,0 +1,31 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Cpu scaling governors on Thinkpax X220</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Cpu scaling governors on Thinkpad X220</h2> +<h3>2025-03-26</h3> +<h4>There are only two CPU scaling governors on my Thinkpad X220</h4> +<p>Powersave, and Performance</p> +<p>Attempting to use any other in tlpui will not give the desired results</p> +<p>Check available govenors:</p> +<code>cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors</code> +<p>See also: <a href="http://wittamore.fr/articles/2025/Linux-power-consumption-control.html">Linux +power consumption control</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/DNS4U-European-DNS-servers-go-live.html b/articles/2025/DNS4U-European-DNS-servers-go-live.html new file mode 100755 index 0000000..442985e --- /dev/null +++ b/articles/2025/DNS4U-European-DNS-servers-go-live.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>DNS4U European DNS servers go live</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>DNS4U European DNS servers go live</h2> +<h3>2025-06-16</h3> +<h4>The EU now has an alternative to Google and Cloudflare DNS</h4> +<p><a href="https://www.joindns4.eu/learn/dns4eu-public-service-launched">JopinDNS launched</a></p> +<p><a href="https://www.joindns4.eu/for-public">JoinDNS for public</a></p> +<p>The offers are Unfiltered resolution, Protective resolution, Protective with Child Protection, +Protective with Ad blocking, Protective with Child protection and Ad blocking</p> + + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/DWM-Dunst-and-Thinkpad-audio-keys.html b/articles/2025/DWM-Dunst-and-Thinkpad-audio-keys.html new file mode 100755 index 0000000..ee184b8 --- /dev/null +++ b/articles/2025/DWM-Dunst-and-Thinkpad-audio-keys.html @@ -0,0 +1,113 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>DWM Dunst and Thinkpad audio keys</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>DWM Dunst and Thinkpad audio keys</h2> +<h3>2025-04-24</h3> +<h4>How I got them working, microphone mute led included</h4> +<p>DWM isn't a desktop environment, so there are a few things to add, like notifications and +audio keys.</p> +<p>I installed dunst and acpid. Dunst is managed by systemd, if you edit ~/.config/dunst/dunstrc +restart dunst with <b>systemctl --user restart dunst.service</b></p> +<p><a href="https://github.com/dunst-project/dunst">Dunst web page</a></p> +<p><a href="https://wiki.archlinux.org/title/Acpid">Acpid Arch page</a></p> + + +<h4>dwm/config.h</h4> +<p>The audio keys are registered in <b>dwm/config.h</b> +<pre> +... +{ 0, XF86XK_AudioMicMute, spawn, SHCMD("~/.local/bin/dwm-audio mictoggle") }, +{ 0, XF86XK_AudioMute, spawn, SHCMD("~/.local/bin/dwm-audio voltoggle") }, +{ 0, XF86XK_AudioLowerVolume, spawn, SHCMD("~/.local/bin/dwm-audio volquieter") }, +{ 0, XF86XK_AudioRaiseVolume, spawn, SHCMD("~/.local/bin/dwm-audio vollouder") }, +... +</pre> + +<h4>~/.local/bin/dwm-audio</h4> +<p>The audio keys run a bash script which sets volume, speaker mute, and microphone mute.</p> +<pre> +#!/bin/bash +# Manage Thinkpad Audio keys + +msgTag="Audio" + +# Microphone +if [[ "$@" == "mictoggle" ]]; then + pamixer --default-source -t + micmute="$(pamixer --default-source --get-mute)" + if [[ "$micmute" == "true" ]]; then + # Mic muted notification + dunstify -t 1000 -a "Microphone" -u low -i audio-input-microphone \ + -h string:x-dunst-stack-tag:$msgTag "Microphone muted" + else + # Mic unmuted notification + dunstify -t 1000 -a "Microphone" -u low -i audio-input-microphone \ + -h string:x-dunst-stack-tag:$msgTag "Microphone unmuted" + fi + exit 0 +fi + +# Speakers +if [[ "$@" == "voltoggle" ]]; then + pamixer --toggle-mute +elif [[ "$@" == "vollouder" ]]; then + pamixer -i 10 +elif [[ "$@" == "volquieter" ]]; then + pamixer -d 10 +fi + +volume="$(pamixer --get-volume-human)" +mute="$(pamixer --get-mute)" +if [[ $volume == 0 || "$mute" == "true" ]]; then + # Sound muted notification + dunstify -t 1000 -a "changeVolume" -u low -i audio-speakers \ + -h string:x-dunst-stack-tag:$msgTag "Volume muted" +else + # Volume notification + dunstify -t 1000 -a "changeVolume" -u low -i audio-speakers \ + -h string:x-dunst-stack-tag:$msgTag -h int:value:"$volume" "Volume: ${volume}" +fi +</pre> + +<h4>Microphone mute led</h4> +<p>I'm running Arch Linux on a Thinkpad X220, so I need to make the mic/mute button's led +lights up when muted. This can't be done with normal user permissions, so I installed acpid. +On Arch linux all acpi events are passed to <b>/etc/acpi/handler.sh</b>, so I added another +function to this file. +</p> +<pre> +... +button/micmute) + if [[ "$(cat /sys/devices/platform/thinkpad_acpi/leds/platform\:\:micmute/brightness)" == "1" ]]; then + echo "0" > /sys/devices/platform/thinkpad_acpi/leds/platform\:\:micmute/brightness + else + echo "1" > /sys/devices/platform/thinkpad_acpi/leds/platform\:\:micmute/brightness + fi +;; +... +</pre> + +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/DWM-circulate-through-tags.html b/articles/2025/DWM-circulate-through-tags.html new file mode 100755 index 0000000..c80535f --- /dev/null +++ b/articles/2025/DWM-circulate-through-tags.html @@ -0,0 +1,196 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>DWM circulate through tags</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>DWM circulate through tags</h2> +<h3>2025-05-07</h3> +<h4>Small modifications to DWM source code so far.</h4> +<p><i>This is intended for my own records, I don't use git yet, perhaps I should look into it.</i></p> +<p>I haven't added any patches to my dwm 6.5 source code, but I have modified dwm.c to enable circulating through tags, +and edited config.h to play nicely with Pywal16 and my French keyboard.</p> +<p>Super+Shift+Left/Right keys circulate through tags<br/> +Super+left/Right keys circulate through windows in a tag.</p> +<h4>dwm.c</h4> +<p>I added this at line 235</p> +<pre> +static void view_adjacent(const Arg *arg); +</pre> +<p>I added this function at line 2067</p> +<pre> +void +view(const Arg *arg) +{ + if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); +} +</pre> +<h4>config.h</h4> +<p>Here is my modified config.h</p> +<pre> +/* See LICENSE file for copyright and license details. */ +#include <X11/XF86keysym.h> + +/* appearance */ +static const unsigned int borderpx = 1; /* border pixel of windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = { "JetBrainsMono Nerd Font Mono:size=11" }; +static const char dmenufont[] = "JetBrainsMono Nerd Font Mono:size=11"; + +/* Pywal16 */ +#include "/home/philip/.cache/wal/colors-wal-dwm.h" + +/* tagging */ +static const char *tags[] = { "Ⅰ" ,"Ⅱ", "Ⅲ", "Ⅳ"}; //, "5", "6", "7", "8", "9" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "ghostty", NULL, NULL, 1 << 0, 0, -1 }, + { "librewolf", NULL, NULL, 1 << 1, 0, -1 }, + { "Chromium", NULL, NULL, 1 << 1, 0, -1 }, + { "Gimp", NULL, NULL, 1 << 2, 1, -1 }, + { "gThumb", NULL, NULL, 1 << 2, 1, -1 }, + { "Galculator", NULL, NULL, 1 << 8, 1, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "Ƭ", tile }, /* first entry is default */ + { "Φ", NULL }, /* no layout function means floating behavior */ + { "µ", monocle }, /* everything is maximised */ +}; + +/* key definitions */ +/* modifiers are ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask and Mod5Mask */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", norm_bg, "-nf", norm_fg, "-sb", sel_bg, "-sf", sel_fg, NULL }; +static const char *termcmd[] = { "ghostty", NULL }; +static const char *wwwcmd[] = { "librewolf", NULL }; +static const char *brighter[] = { "brightnessctl", "set", "10%+", NULL }; +static const char *dimmer[] = { "brightnessctl", "set", "10%-", NULL }; +static const char *bluetooth[] = { "blueman-manager", NULL }; +static const char *scrotcmd[] = { "screenshot.sh", NULL}; + +static const Key keys[] = { + /* modifier key function argument */ + { 0, XF86XK_AudioMute, spawn, SHCMD("~/.local/bin/dwm-audio voltoggle") }, + { 0, XF86XK_AudioLowerVolume, spawn, SHCMD("~/.local/bin/dwm-audio volquieter") }, + { 0, XF86XK_AudioRaiseVolume, spawn, SHCMD("~/.local/bin/dwm-audio vollouder") }, + { 0, XF86XK_AudioMicMute, spawn, SHCMD("~/.local/bin/dwm-audio mictoggle") }, + { 0, XF86XK_MonBrightnessDown, spawn, {.v = dimmer } }, + { 0, XF86XK_MonBrightnessUp, spawn, {.v = brighter } }, + { 0, XF86XK_Launch1, spawn, SHCMD("rofi -show power-menu -modi power-menu:rofi-power-menu") }, + { 0, XK_Menu, spawn, SHCMD("rofi -show power-menu -modi power-menu:rofi-power-menu") }, + { 0, XK_Print, spawn, {.v = scrotcmd } }, + { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_r, spawn, SHCMD("rofi -show run") }, + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_w, spawn, {.v = wwwcmd } }, + { MODKEY, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, XK_Right, focusstack, {.i = +1 } }, + { MODKEY, XK_Left, focusstack, {.i = -1 } }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY, XK_agrave, view, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, XK_semicolon, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_b, spawn, {.v = bluetooth } }, + { MODKEY|ShiftMask, XK_c, killclient, {0} }, + { MODKEY|ShiftMask, XK_Right, view_adjacent, {.i = +1 } }, + { MODKEY|ShiftMask, XK_Left, view_adjacent, {.i = -1 } }, + { MODKEY|ShiftMask, XK_Return, zoom, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY|ShiftMask, XK_agrave, tag, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_semicolon, tagmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_q, quit, {0} }, + { MODKEY|ShiftMask, XK_x, spawn, SHCMD("rofi -show power-menu -modi power-menu:rofi-power-menu") }, + TAGKEYS( XK_ampersand, 0) + TAGKEYS( XK_eacute, 1) + TAGKEYS( XK_quotedbl, 2) + TAGKEYS( XK_apostrophe, 3) + TAGKEYS( XK_parenleft, 4) + TAGKEYS( XK_minus, 5) + TAGKEYS( XK_egrave, 6) + TAGKEYS( XK_underscore, 7) + TAGKEYS( XK_ccedilla, 8) + TAGKEYS( XK_agrave, 9) +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, + { ClkTagBar, 0, Button4, view_adjacent, {.i = -1 } }, + { ClkTagBar, 0, Button5, view_adjacent, {.i = +1 } }, +}; + +</pre> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/DWM-slstatus.html b/articles/2025/DWM-slstatus.html new file mode 100755 index 0000000..7ebef78 --- /dev/null +++ b/articles/2025/DWM-slstatus.html @@ -0,0 +1,46 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>DWM slstatus</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>DWM slstatus</h2> +<h3>2025-04-25</h3> +<h4>KISS</h4> +<p><img src="/images/blog/slstatus.png" alt="slstatus bar image"></p> +<p><a href="https://tools.suckless.org/slstatus/">https://tools.suckless.org/slstatus/</a></p> +<p>After fiddling with dwm-blocks and dwm-bar, I decided that the only thing I want to see +in my status bar is battery info and date/time. The rest can be done with btop and alerts.</p> +<p>So I cloned the slstatus source code from suckless.org and added the following in slstatus/config.h</p> +<pre> +static const struct arg args[] = { + /* function format argument */ + { battery_remaining, "%s", "BAT0" }, + { battery_state, " %s", "BAT0" }, + { battery_perc, " %s%%", "BAT0" }, + { datetime, " | %s", "%a %d-%m %T" }, +}; +</pre> +<p>As I'm using the Jetbrains mono nerdfont in DWM I added some icons in slstatus/components/battery.c</p> +<p><img src="/images/blog/battery.c.png" alt="battery code"></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Daft-poems.html b/articles/2025/Daft-poems.html new file mode 100755 index 0000000..a4b1e93 --- /dev/null +++ b/articles/2025/Daft-poems.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Poems</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Poems</h2> +<h3>2025-08-22</h3> +<h4>Playing with words</h4> + +<p><a href="https://git.wittamore.fr/poetry/tree/">Some poems</a></p> + +<p>No pretention here, just learning. Let me know if I should keep my day job :-)</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Debian-12-to-13-upgrade.html b/articles/2025/Debian-12-to-13-upgrade.html new file mode 100755 index 0000000..3fddef5 --- /dev/null +++ b/articles/2025/Debian-12-to-13-upgrade.html @@ -0,0 +1,32 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Debian 12 to 13 upgrade</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Debian 12 to 13 upgrade</h2> +<h3>2025-08-27</h3> +<h4>Server upgrade successful</h4> +<p>I have upgraded my server to the latest Debian version, Trixie (13) without problem.</p> +<p>Mailcow have posted some simple steps to follow <a href="https://mailcow.email/posts/2025/upgrade-deb12-to-deb13/">here</a></p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Detrumpez-vous.html b/articles/2025/Detrumpez-vous.html new file mode 100755 index 0000000..f967557 --- /dev/null +++ b/articles/2025/Detrumpez-vous.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Détrumpez-vous</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Detrumpify</h2> +<h3>2025-05-03</h3> +<h4>Technology against politics</h4> +<p>An interesting addition for smartphone driven boycotts</p> +<p><a href="https://detrumpez-vous.com/">Web site</a></p> +<p>Détrumpez-vous is an app for iPhone and Android that signals products that are of American origin.</p> +<p>I'm going to test this on my weekly shopping.</p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Dmenu-updates.html b/articles/2025/Dmenu-updates.html new file mode 100755 index 0000000..4eba85d --- /dev/null +++ b/articles/2025/Dmenu-updates.html @@ -0,0 +1,46 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Dmenu updates</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Dmenu updates</h2> +<h3>2025-06-16</h3> +<h4>more patches added to dmenu</h4> +<p>I've updated my dmenu fork, it now has the following patches:</p> + +<ul> + <li>xresources</li> + <li>center</li> + <li>border</li> + <li>xyz</li> + <li>fuzzymatch</li> + <li>fuzzyhighlight</li> + <li>mouse and motions support</li> +</ul> + +<p>I've also tweaked the patches to work with xresources.</p> + +<p><a href="https://git.wittamore.fr/dmenu/about/?h=master">my dmenu git repository</a></p> + + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Dwmblocks.html b/articles/2025/Dwmblocks.html new file mode 100755 index 0000000..1c2d2d8 --- /dev/null +++ b/articles/2025/Dwmblocks.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Dwmblocks</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Dwmblocks</h2> +<h3>2025-06-02</h3> +<h4>eating previous thoughts</h4> +<p>I wanted to keep my status bar simple, and slstatus fitted the bill</p> +<p>But I was missing an indication of the CPU temperature as I got tired of using conky +as it was only visible on empty DWM tags, and I didn't want to try writing a program in C to do it.</p> +<p>So a new status monitor was needed. I liked the idea of dwmblocks firing up bash scripts, +but I tried the original and several forks without luck. I finally settled on +dwmblocks-async, because it is more recent and ... it worked.</p> +<p><a href="https://github.com/UtkarshVerma/dwmblocks-async">Dwmblocks-async</a></p> +<p><a href="https://git.wittamore.fr/scripts/tree/pw-date">An example script</a></p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Enable-Arch-Extra-repository-in-Artix.html b/articles/2025/Enable-Arch-Extra-repository-in-Artix.html new file mode 100755 index 0000000..44fc86e --- /dev/null +++ b/articles/2025/Enable-Arch-Extra-repository-in-Artix.html @@ -0,0 +1,49 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Enable Arch Extra repository in Artix</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Enable Arch Extra repository in Artix</h2> +<h3>2025-06-20</h3> +<h4>Extending Artix package library</h4> +<p>Artix Linux is mostly Arch Linux, minus systemd of course, and plus packages that +replace it's absence.</p> +<p>In order to access packages that may not be available via Artix, it's possible to add +the Arch Linux Extra Mirror.</p> + +<code>pacman -S artix-archlinux-support +wget https://archlinux.org/mirrorlist/all/ -O /etc/pacman.d/mirrorlist-arch +</code> + +<p>Edit /etc/pacman.conf and activate the mirror.</p> + +<code>[extra] +Include = /etc/pacman.d/mirrorlist-arch</code> + +<p>Populate the Arch Linux keyring and synchronize the repository</p> + +<code>pacman-key --populate archlinux +pacman -Syu +</code> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Eza.html b/articles/2025/Eza.html new file mode 100755 index 0000000..8709fb3 --- /dev/null +++ b/articles/2025/Eza.html @@ -0,0 +1,32 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Eza</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Eza</h2> +<h3>2025-03-24</h3> +<h4>Eza is a replacement for ls that uses symbols that are found in nerdfonts</h4> +<p><img src="/images/blog/eza.png"></p> +<p>I edited my .bashrc to alias ls to eza like so:</p> +<code>alias ls="eza --icons=always" +alias ll="eza --icons=always -l" +alias la="eza --icons=always -la" +</code> +<p>Pretty</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Fixing-my-dad's-Stanley-8-CFW-lock-plane.html b/articles/2025/Fixing-my-dad's-Stanley-8-CFW-lock-plane.html new file mode 100755 index 0000000..4e5f9e6 --- /dev/null +++ b/articles/2025/Fixing-my-dad's-Stanley-8-CFW-lock-plane.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Fixing my dad's Stanley 8 CFW lock plane</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Fixing my dad's Stanley 8 CFW lock plane</h2> +<h3>2025-08-27</h3> +<h4>Cast iron fixes</h4> +<p>I inherited a small low angle block plane from my dad.</p> +<p>Nice looking with brass buttons, but upon inspection the blade wasn't straight. +I removed it and checked, it was square and just needed sharpening, but when I looked at the cast iron base, +the seat of the blade at throat level was thicker on one side, making it impossible to align.</p> +<p>The sides were also both slightly more that 90° which didn't help.</p> +<p>The easiest solution was to remove the adjustable front throat and reform the blade seat with a file.</p> +<p>It worked, and after flattening the foot of the plane and sides using sandpaper on a granit slab, + then sharpening the blade I now have a very nice block plane.</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Flowers-for-the-bees.html b/articles/2025/Flowers-for-the-bees.html new file mode 100755 index 0000000..daf0327 --- /dev/null +++ b/articles/2025/Flowers-for-the-bees.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Flowers for the bees</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Flowers for the bees</h2> +<h3>2025-04-12</h3> +<h4>I sowed some flower seeds</h4> +<p>I wouldn't mind having half the garden for wild flowers, although I'm not +sure what the maintenance would be like, there's a lot of native flora that is +a pain here in Brittany.</p> +<p>Anyway I sowed a small patch to see what happens, a trial run. I'm sure the bees +will appreciate.</p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/France-rejects-backdoors.html b/articles/2025/France-rejects-backdoors.html new file mode 100755 index 0000000..36488fd --- /dev/null +++ b/articles/2025/France-rejects-backdoors.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>France rejects backdoors</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>France rejects backdoors</h2> +<h3>2025-03-22</h3> +<h4>It's always a warm fuzzy feeling when we do something smart</h4> +<p><a href="https://www.eff.org/deeplinks/2025/03/win-encryption-france-rejects-backdoor-mandate">eff.org</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Ghostty.html b/articles/2025/Ghostty.html new file mode 100755 index 0000000..cf55cc9 --- /dev/null +++ b/articles/2025/Ghostty.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Ghostty</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Ghostty</h2> +<h3>2025-03-27</h3> +<h4>I've been playing with Ghostty, a new terminal emulator that seems very promising</h4> +<p><img width="50%" src="/images/blog/ghostty.gif"></p> +<p>Here's <a href="https://newsboat.org/">Newsboat</a> opening an article with <a href="https://sr.ht/~bptato/chawan/">Chawan</a> inside <a href="https://ghostty.org/"">Ghostty</a>:</p> +<p><img src="/images/blog/chawan-ghostty.png" alt="news article in chawan"></p> +<h4>Tips</h4> +<p>When I ssh into a vps I found some keys where not working in mc (midnight commander). The solution was to edit ~/.ssh/config and add:</p> +<p><code>Host * + setenv Term=xterm-256color</code></p> +<h4>Ressources</h4> +<p><a href="https://ghostty.org/">Ghostty</a></p> +<p><a href="https://github.com/fearlessgeekmedia/awesome-ghostty">Awasome Ghostty</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Git-repository.html b/articles/2025/Git-repository.html new file mode 100755 index 0000000..ccb829a --- /dev/null +++ b/articles/2025/Git-repository.html @@ -0,0 +1,32 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Git repository</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Git repository</h2> +<h3>2025-05-07</h3> +<h4>I've added a git repository</h4> +<p>Nothing much there yet, but I've got cgit running...</p> +<p><a href="https://git.wittamore.fr">https://git.wittamore.fr</a></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Gopher-it.html b/articles/2025/Gopher-it.html new file mode 100755 index 0000000..04ea5cb --- /dev/null +++ b/articles/2025/Gopher-it.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Gopher it</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Gopher it</h2> +<h3>2025-09-14</h3> +<h4>My Gopher hole</h4> +<p>After all I've said against Gopher and Gemini, I've started my own Gopher hole.</p> +<p>Just proves humans are fickle and you shouldn't listen to my rants and I'm an incorrigible fiddler.</p> +<p>You'll need a gopher browser such as <a href="https://gmi.skyjake.fi/lagrange/">Lagrange (GUI)</a> or +<a href="https://lynx.browser.org/">Lynx (TUI)</a> for this link: <a href="gopher://nagatha.fr">gopher://nagatha.fr</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Greycoders-list-of-Text-only-minimalist-web-sites.html b/articles/2025/Greycoders-list-of-Text-only-minimalist-web-sites.html new file mode 100755 index 0000000..70a68c7 --- /dev/null +++ b/articles/2025/Greycoders-list-of-Text-only-minimalist-web-sites.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Greycoders list of Text only minimalist web sites</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Greycoders list of minimalist web sites 2025</h2> +<h3>2025-04-03</h3> +<h4>Avoiding web pages filled with ads, videos, and bandwidth-heavy content</h4> +<p><a href="https://greycoder.com/a-list-of-text-only-new-sites/">https://greycoder.com</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Hard-disk-fail.html b/articles/2025/Hard-disk-fail.html new file mode 100755 index 0000000..7e341c2 --- /dev/null +++ b/articles/2025/Hard-disk-fail.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Hard disk fai</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Hard disk fail</h2> +<h3>2025-03-19</h3> +<h4>The hard disk on my X220 failed</h4> +<p>and my backup machine was in an unstable state for a while and I hadn't fixed it. Examination of my collection of USB keys found no linux installer, so I was stumped through lazyness.</p> +<p>But at the bottom of a pencil holder I found another usb key with an old copy of Linux Mint which enabled me to download Arch and recover.</p> +<p>Luckily all my inportant files were saved, and I use Bitwarden too, so with a new SSD I was quickly back up and running.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Headless-Arch-Install.html b/articles/2025/Headless-Arch-Install.html new file mode 100755 index 0000000..8852691 --- /dev/null +++ b/articles/2025/Headless-Arch-Install.html @@ -0,0 +1,40 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Headless Arch Install</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Headless Arch Install</h2> +<h3>2025-03-21</h3> +<h4>I have a micro pc I'd like to use as a web server</h4> +<p>It doesn't have a screen so I'll need to do a headless installation</p> +<p>I found some simple instructions here: <a href="https://tarneo.fr/posts/arch_headless/">https://tarneo.fr/posts/arch_headless/</a></p> +<br> +<code>sudo pacman -S archiso +mkdir arch-headless && cd arch-headless +cp -r /usr/share/archiso/configs/releng/ . +mkdir releng/airootfs/root/.ssh/ +cp ~/.ssh/id_rsa.pub releng/airootfs/root/.ssh/authorized_keys +sudo mkarchiso -v -o ./out ./releng +</code> +Insert a usb key, mine is in /dev/sdc, and create a bootable usb drive +<code>dd if=./out/archlinux-2025.03.21-x86_64.iso of=/dev/sdc bs=4M; sync</code> +<br> +<p>Find the IP address of the machine, and ssh to it. You can then run +archinstall for example.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Html-center-element.html b/articles/2025/Html-center-element.html new file mode 100755 index 0000000..50cdf83 --- /dev/null +++ b/articles/2025/Html-center-element.html @@ -0,0 +1,42 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Html center element</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Html center element</h2> +<h3>2025-04-10</h3> +<h4>Making things simpler still</h4> +<p>The <b>center</b> element in html is depreciated, and validation of the page shows warnings. +Instead the use of the css style <b>text-align:center</b> is precognised.</p> +<p>But this is ignored in terminal text browsers, and I prefer some sort of consistency, so I've removed center elements and text-align css. +The page itself is centered setting margins. Chawan gives the best rendering on the terminal, here I'm using Ghostty.</p> +<h3>Librefox</h3> +<p><img style="border: 1px solid gray" width="60%" src="/images/blog/librefox-wittamore-1.png" alt="image of blog in Librefox"></p> +<h3>Chawan (terminal)</h3> +<p><img style="border: 1px solid gray" width="60%" src="/images/blog/cha-wittamore-1.png" alt="image of blog in Chawan"></p> +<h3>W3M (terminal)</h3> +<p><img style="border: 1px solid gray" width="60%" src="/images/blog/w3m-wittamore-1.png" alt="image of blog in W3M"></p> +<h3>Links (terminal)</h3> +<p><img style="border: 1px solid gray" width="60%" src="/images/blog/links-wittamore-1.png" alt="image of blog in Links"></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Install-the-Chawan-text-browser-on-Artix.html b/articles/2025/Install-the-Chawan-text-browser-on-Artix.html new file mode 100755 index 0000000..0136bb9 --- /dev/null +++ b/articles/2025/Install-the-Chawan-text-browser-on-Artix.html @@ -0,0 +1,42 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Install the Chawan text browser on Artix</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Install the Chawan text browser on Artix</h2> +<h3>2025-06-15</h3> +<h4>the Arch package doesn't compile, here's the solution</h4> +<p>The package chawan-git in the AUR doesn't compile as the included NIM version is too old</p> +<p>Here's how I installed it in <a href="https://artixlinux.org/">Artix Linux</a></p> +<p>Install the NIM compiler from <a href="https://nim-lang.org/install.html"> https://nim-lang.org/install.html</a></p> +<p>The default dependancies were already installed on my hard disk: (OpenSSL, libssh2, brotli)</p> +<code>cd ~/src +git clone https://git.sr.ht/~bptato/chawan && cd chawan +make +sudo make install +cha -V</code> +<p>And it's done. I use mostly use chawan as newsboat's browser, it's really good.</p> +<p>Thanks to <a href="https://github.com/kachick/chawan?tab=readme-ov-file">Kachick</a></p> +<p>Previous article: <a href="/articles/2025/Newsboat-browser:-rdrview-or-chawan.html">rdrview or chawan</a></p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Jazz-radio-recording-from-1976.html b/articles/2025/Jazz-radio-recording-from-1976.html new file mode 100755 index 0000000..56f8517 --- /dev/null +++ b/articles/2025/Jazz-radio-recording-from-1976.html @@ -0,0 +1,47 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>Jazz radio recording from 1976</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + + <main> + <article> + <h2>Jazz radio recording from 1976</h2> + <h3>2025-05-24</h3> + <h4>KBCA Jazz Radio - Los Angeles<br />Recorded February 1976</h4> + <p>An enjoyable 3 hour radio time machine</p> + <div class="wrapper"> + <iframe + src="https://www.youtube.com/embed/fwTDd852aPM?list=PLH8psnsc_qVuVZW5-qX4-PPZHZHzbvKx4" + title="KBCA Jazz Radio - Los Angeles - Recorded February 1976- Vintage 70's Jazz Radio" + frameborder="0" + allow=" + accelerometer; + autoplay; + clipboard-write; + encrypted-media; + gyroscope; + picture-in-picture; + web-share; + " + referrerpolicy="strict-origin-when-cross-origin" + allowfullscreen + ></iframe> + </div> + </article> + </main> + <footer> + <p> </p> + + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2025/Koreader-for-e-ink-devices.html b/articles/2025/Koreader-for-e-ink-devices.html new file mode 100755 index 0000000..107c886 --- /dev/null +++ b/articles/2025/Koreader-for-e-ink-devices.html @@ -0,0 +1,32 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Koreader for e-ink devices</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Koreader for e-ink devices</h2> +<h3>2025-03-24</h3> +<h4>I installed Koreader on my Kobo Clara Colour last night</h4> +<p>Like everything, there's a learning curve due to the multiplicity of options. The first thing I did was copy the Bookerly font to the Koreader fonts folder on the Kobo, as I find the default Noto fonts too ugly for reading.</p> +<p>The second thing I did was remove the Koreader installation log file and +logo which stuck out like a sore thumb in the file manager</p> +<p>The third thing I have to do is read the user guide...</p> +<p>Anyhow reading is a pleasure once I fixed the font, font size, and line spacing</p> +<p><a href="http://koreader.rocks/">Koreader +<p><a href="https://www.mobileread.com/forums/showthread.php?t=314220">One-Click Install Packages for KOReader & Plato</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Ladybird-web-browser.html b/articles/2025/Ladybird-web-browser.html new file mode 100755 index 0000000..0ce8563 --- /dev/null +++ b/articles/2025/Ladybird-web-browser.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Ladybird web browser</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Ladybird web browser</h2> +<h3>2025-06-26</h3> +<h4>L'éspoir fait vivre</h4> +<p>The Ladybird web browser is truly something different, and a hope for the future.</p> +<p>It has no connection to Google, no rehash of older web engines, with no corporate influence looming over it.</p> +<p>Written transparently, W3C standards compliant, it will be available for public beta in 2026, with release is planned for 2028</p> +<p>There's an aphid eating light at the end of the tunnel.</p> +<p><a href="https://ladybird.org/">https://ladybird.org/</a></p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Les-Oiseaux-des-Jardins.html b/articles/2025/Les-Oiseaux-des-Jardins.html new file mode 100755 index 0000000..a705631 --- /dev/null +++ b/articles/2025/Les-Oiseaux-des-Jardins.html @@ -0,0 +1,32 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Les Oiseaux des Jardins</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Les Oiseaux des Jardins</h2> +<h3>2025-04-04</h3> +<h4>Identify garden birds in Brittany</h4> +<p>My bird feeder only has Blue Tits, Coal Tits, and sparrows - but you never know.</p> +<p><img src="/images/blog/poster-oiseaux-des-jardins.png" alt="poster-les-oiseaux-des-jardins"></p> +<p><a href="https://www.bretagne-vivante.org/decouverte-de-la-nature/comptage-oiseaux-des-jardins/">Bretagne Vivant - comptage des oiseaux</a></p> +<p>Bretagne Vivante has been Brittany's leading association for the protection of nature and biodiversity since 1959. +Recognized as a non-profit organization, it works on a daily basis to improve knowledge and preservation of the region's natural heritage. +It also manages a network of protected sites and nature reserves throughout Brittany and Loire-Atlantique. +Passing on and sharing its passions is also at the heart of Bretagne Vivante's missions.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Linux-System-Performance-Tuning.html b/articles/2025/Linux-System-Performance-Tuning.html new file mode 100755 index 0000000..7dcd6a4 --- /dev/null +++ b/articles/2025/Linux-System-Performance-Tuning.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Linux System Performance Tuning</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Linux System Performance Tuning</h2> +<h3>2025-03-26</h3> +<h4>Linux System Performance Tuning: Optimizing CPU, Memory, and Disk</h4> + +<p><a href="https://www.linuxjournal.com/content/linux-system-performance-tuning-optimizing-cpu-memory-and-disk">Linuxjournal</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Linux-power-consumption-control.html b/articles/2025/Linux-power-consumption-control.html new file mode 100755 index 0000000..31748e0 --- /dev/null +++ b/articles/2025/Linux-power-consumption-control.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Linux power consumption control</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Linux power consumption control</h2> +<h3>2025-03-23</h3> +<h4>TLP is a feature-rich command line utility for Linux</h4> +<p>saving laptop battery power without the need to delve deeper into technical details.</p> +<p>TLP’s default settings are already optimized for battery life, so you may install it and just sit back and relax. Nevertheless, TLP is completely customizable to get even more power savings or meet your exact requirements.</p> +<code>sudo pacman -S tlp tlpui cpupower</code> +<code>sudo systemctl enable tlp.service</code> +<p>I enabled the panel cpufreq plugin on my Thinkpad X220 and saw that AC/BAT power changes +were handled correctly</p> +<p><img src="/images/blog/xfce4-panel-cpu-fan.png"></p> +<p><a href="https://linrunner.de/tlp/index.php">TLP Web site</a></p> +<p><a href="https://wiki.archlinux.org/title/TLP">Arch Linux TLP page</a></p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Lynx,-Gopher,-and-video-files.html b/articles/2025/Lynx,-Gopher,-and-video-files.html new file mode 100755 index 0000000..6288414 --- /dev/null +++ b/articles/2025/Lynx,-Gopher,-and-video-files.html @@ -0,0 +1,102 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Lynx, Gopher, and video files</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Lynx, Gopher, and video files</h2> +<h3>2025-09-21</h3> +<h4>Getting Lynx to open video files</h4> + +<p>I've been using Lynx as a Gopher browser on Artix Linux, +and I like it a lot as it renders cleanly, has logical +keybindings, and enables following html links.</p> +<p>But...</p> +<p>Trying to open video files with an external application +wasn't working. Luckily on one test I saw an error +concerning "mpeg_play".</p> + +<p>The only mention of this file is as a VIEWER entry in +lynx.cfg. But changing it to something else has no effect +at all.</p> + +<p>Somewhere in lynx, mpeg_play is hardcoded. 🤔. Checking the Lynx source code, I found it in HTInit.c</p> +<p>Why short circuit the config file this way? Anyway, I wrote a bash script named mpeg_play:</p> + +<p> +#!/usr/bin/env bash<br> +/usr/bin/mpv --title="Mpv" >/dev/null 2>&1 $1 +</p> + +<p>chmod +x mpeg_play and put it in my PATH</p> + +<p>and it works!<br> +Lynx can now play media files in Gopher holes. +(Note the --title parameter is to get it to play nice with dwm).</p> + +<p>The question remains: why is this hard coded?</p> + +<p>As for mpeg_play, it appears in Linux mailcap +entries in the 90's and seems to have been bundled +in ximagetools.</p> + +<p>Mpeg_play appears to date back a long way as a UNIX application:</p> + +<p><a href="http://www.geom.uiuc.edu/software/mpeg_play">http://www.geom.uiuc.edu/software/mpeg_play</a></p> + +<h3>Update 2025-09-23</h3> + +<p>A little tweak made this script capable of opening playlists</h3> + +<p>----------</p> +<p> +#!/usr/bin/env bash<br> +# necessary for Lynx browser video files<br> +# chmod +x and place in your PATH<br> +<br> +# mpv can sometimes take a few seconds to open when streaming<br> +# so I use notification that it's happening<br> +<br> +dunstify -t 2000 -a "Video" -u low -i "media-playback-start-symbolic"\<br> + -h string:x-dunst-stack-tag:"Media" "MPV Launching" &<br> +<br> +# detect if playlist<br> +# Lynx uses an anonymous file in /tmp, so we can only<br> +# detect a file type by it's contents, not it's extension<br> +<br> +if [ "$(grep -c "EXT3MU" "$1")" -eq 1 ]; then<br> + # this is a playlist<br> + # I use dwm, the mpv options used here make it compatible<br> + mpv --geometry=1068x600 --ontop --idle=yes --force-window=yes\<br> + --playlist="$1" >/dev/null 2>&1 &<br> +else<br> + mpv --geometry=1068x600 --ontop --idle=yes --force-window=yes\<br> + "$1" >/dev/null 2>&1 &<br> +fi<br> +<br> +exit 0<br> +</p> +<p>----------</p> +<p>The latest version can be found at <a href="https://git.wittamore.fr">git.wittamore.fr</a></p> + + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Mailcow-Docker-too-many-requests.html b/articles/2025/Mailcow-Docker-too-many-requests.html new file mode 100755 index 0000000..619879a --- /dev/null +++ b/articles/2025/Mailcow-Docker-too-many-requests.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Mailcow Docker too many requests</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Mailcow Docker too many requests</h2> +<h3>2025-04-29</h3> +<h4>Dcker changes</h4> +<p>While updating Mailcow this morning Docker gave me a "too many requests" error.</p> +<p>It seems that Docker now limits the IPv6’s subnet instead of the IPv6 address itself, which +could mean your pull limit is shared.</p> +<p>To fix this you can create a personal account at docker.com, this will allow 200 image pulls per 6 hours. +My Mailcow installation does 7 pulls for an update.</p> +<p><a href="https://hub.docker.com/signup">Docker account creation</a></p> +<p>Once the account created, copy the personal access token, go back to your server and enter docker login -u (login)</p> +<p>Mailcow will now update without error.</p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Mailcow-with-Nginx-reverse-proxy-(updated).html b/articles/2025/Mailcow-with-Nginx-reverse-proxy-(updated).html new file mode 100755 index 0000000..efbdc99 --- /dev/null +++ b/articles/2025/Mailcow-with-Nginx-reverse-proxy-(updated).html @@ -0,0 +1,31 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Mailcow with Nginx reverse proxy (updated)</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Mailcow with Nginx reverse proxy (updated)</h2> +<h3>2025-09-11</h3> +<h4>Improved instructions with DANE and MTS-STA info</h4> +<p>-> <a href="/articles/2025/Mailcow-with-Nginx-reverse-proxy.html">Link to the updated post</a></p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Mailcow-with-Nginx-reverse-proxy.html b/articles/2025/Mailcow-with-Nginx-reverse-proxy.html new file mode 100755 index 0000000..657b214 --- /dev/null +++ b/articles/2025/Mailcow-with-Nginx-reverse-proxy.html @@ -0,0 +1,207 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Mailcow with Nginx reverse proxy</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Mailcow with Nginx reverse proxy (updated)</h2> +<h3>2025-04-19</h3> +<h4>Using other web services with Mailcow</h4> +<p>This is roughly what I did to have an Nginx web server on the same machine as dockerized +<a href="https://mailcow.email/">Mailcow</a></p> +<p><b>Note:</b> mail.xxxxx.com is your mail server (MX), yourdomain.com is whatever domain you wish to use.</p> +<ol> +<li>Change the default http and https ports (for example 8480 and 8443)<br> +and set <b>SKIP_LETS_ENCRYPT=y</b> in /opt/mailcow-dockerized/mailcow.conf</li> +<li>Restart Mailcow by executing <br> +<b>cd /opt/mailcow-dockerized;docker compose restart</b></li> +<li>Make sure the DNS entry mail.xxxxx.com points to the server</li> +<li><b>mkdir -p /var/www/html/letsencrypt/.well-known/acme-challenge</b></li> +<li>Create /etc/nginx/letsencrypt_path. This can be re-used in your other domains. +<pre># url for letsencrypt +location ^~ /.well-known/acme-challenge/ { + allow all; + default_type "text/plain"; + # Path can be used for cert-validation on all domains + root /var/www/html/letsencrypt; + break; +} +</pre> +</li> +<li>Stop the nginx server <b>systemctl stop nginx</b></li> +<li>Run <b>certbot certonly -d mail.xxxxx.com</b> (select 2 standalone)</li> +<li>Create /etc/nginx/sites-available/mail.xxxxx.com</li> +<pre>server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + + server_name mail.xxxxx.com; + + charset UTF-8; + access_log /var/log/nginx/access.mail.xxxxx.com; + error_log /var/log/nginx/error.mail.xxxxx.com; + include snippets/error_pages.conf; + ssl_certificate /etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/mail.xxxxx.com/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams-2048.pem; + + include /etc/nginx/letsencrypt_path; + + location / { + proxy_pass http://127.0.0.1:8480; + proxy_buffering off; + include /etc/nginx/proxy_params; + + } + +} + +server { + + listen 80; + listen [::]:80; + server_name mail.xxxxx.com; + return 301 https://mail.xxxxx.com; +} +</pre> +<li><b>ln -s /etc/nginx/sites-available/mail.xxxxx.com <br>/etc/nginx/sites-enabled/mail.xxxxx.com</b></li> +<li>Test with <b>nginx -t</b>, if all is well run <b>sudo systemctl start nginx</b></li> +<li>Pointing your browser to https://mail.xxxxx.com should show you the Mailcow login page.</li> +</ol> +<h3>Problem</h3> +<p>The problem is that Mailcow can no longer use port 80 to update it's ssl certificates that +are used by postfix and dovecot. Instead certbot puts them in /etc/letsencrypt/live. +To fix this the following script runs from crontab daily.</p> +<pre>#!/usr/bin/env bash + +# we are running behind an nginx proxy, where certbot is run by systemd, +# so the mail.xxxxx.com certificates are updated, but not copied to the mailcow folder +# in case the certificate isn't renewed automatically, run this: +# sudo certbot -n certonly --webroot -w /var/www/html/letsencrypt -d mail.xxxxx.com + +t1="/etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem" +t2="/opt/mailcow-dockerized/data/assets/ssl/mail.xxxxx.com/cert.pem" + +# test if certificate has been updated +differ=$(cmp -b $t1 $t2 | grep -c "differ") + +[[ "$differ" = "0" ]] && exit 0 + +# these files are required in /data/assets/ssh/mail.xxxxx.com +cp /etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem /opt/mailcow-dockerized/data/assets/ssl/mail.xxxxx.com/cert.pem +cp /etc/letsencrypt/live/mail.xxxxx.com/privkey.pem /opt/mailcow-dockerized/data/assets/ssl/mail.xxxxx.com/key.pem + +# these files are required in /data/assets/ssl/ (turns out sending failed otherwise) +cp /etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem /opt/mailcow-dockerized/data/assets/ssl/cert.pem +cp /etc/letsencrypt/live/mail.xxxxx.com/privkey.pem /opt/mailcow-dockerized/data/assets/ssl/key.pem + +# update postfix & docker +docker exec $(/usr/bin/docker ps -qaf name=postfix-mailcow) postfix reload +docker exec $(/usr/bin/docker ps -qaf name=dovecot-mailcow) dovecot reload +</pre> +<p>Inspiration: <a href="https://felixmoessbauer.com/blog-reader/mailcow-reverse-proxy-letsencrypt.html">Felix Moesbauer</a></p> +<p>Don't forget to check and correct if necessary your DANE and MTA-STS records if you use them</p> +<h4>DANE</h4> +<p>On your mail server:</p> +<pre>apt install hash-slinger -y</pre> +<p>Create a TLSA record:</p> +<pre>tlsa --create --selector 1 -p 25 --certificate /etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem mail.xxxxx.com +</pre> +<p>The last line or the result will be something like:</p> +<pre>_25._tcp.mail.xxxxx.com. IN TLSA 3 1 1 443ac7c5c70fbfbc...</pre> +<p>Enter this TLSA line in your mail server's DNS record.</p> +<p>Do the same for the following ports:</p> +<pre>_110._tcp.mail.xxxxx.com +_143._tcp.mail.xxxxx.com +_465._tcp.mail.xxxxx.com +_587._tcp.mail.xxxxx.com +_993._tcp.mail.xxxxx.com +_995._tcp.mail.xxxxx.com +</pre> +<p>You can check your DANE records at <a href="https://www.huque.com/bin/danecheck-smtp">huque.com</a></p> +<p><img src="/images/blog/dane-success.png" alt="dane success"></p> +<h4>MTA-STS</h4> +<p>This is a fallback to DANE, you can run both.</p> +<p><b>Note:</b> https access to mta-sts.yourdomain.com is obligatory</p> +<ol> +<li>Create /var/www/html/mta-sts/.well-known/mta-sts.txt with the following content:<br> +<pre>version: STSv1 +mode: enforce +max_age: 172800 +mx: mail.xxxxx.com +</pre> +</li> +<li>Create the DNS record _mta-sts.mail.xxxxx.com as TXT with "v=STSv1; id=<b>INSERT AN ID, EX. 202511101644</b>"</li> +<li>Create the DNS record _mta-sts.yourdomain.com as CNAME pointing to _mta-sts.mail.xxxxx.com (your mail server)</li> +<li>Create the DNS record mta-sts.yourdomain.com pointing to the ip of the web server</li> +<li>You may need to wait for the DNS records to propagate</li> +<li>Stop Nginx with <b>systemctl stop nginx</b></li> +<li>Create a letsencrypt certificate with <b>certbot certonly -d mta-sts.yourdomain.com</b> (select 2 standalone)</li> +<li>Create mta-sts.yourdomain.com in Nginx:</li> +<li>/etc/sites-available/mta-sts.yourdomain.com<br> +<pre>server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + + charset UTF-8; + access_log /var/log/nginx/access.mts-sta.yourdomain.com; + error_log /var/log/nginx/error.mts-sta.yourdomain.com; + include snippets/error_pages.conf; + ssl_certificate /etc/letsencrypt/live/mta-sts.yourdomain.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/mta-sts.yourdomain.com/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams-2048.pem; + + include /etc/nginx/letsencrypt_path; + + root /var/www/html/mta-sts; + index index.html index.htm; + + server_name mta-sts.yourdomain.com; + + location / { + try_files $uri $uri/ =404; + } +} +server { + listen 80; + listen [::]:80; + server_name mta-sts.yourdomain.com; + return 301 https://mta-sts.yourdomain.com; +} +</pre> +</li> +<li><b>ln -s /etc/nginx/sites-available/mta-sts.yourdomain.com <br>/etc/nginx/sites-enabled/mta-sts.yourdomain.com</b></li> +<li>Test with nginx -t and if all goes well start nginx <b>systemctl start nginx</b></li> +</ol> +<p>You can check your domain's MTA-STS at <a href="https://mxtoolbox.com/mta-sts.aspx">mxtoolbox.com</a><p> +<p> </p> +<p>All this worked for me :-) I hope it does for you!</p> + +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> + + + diff --git a/articles/2025/Menu-editor-for-XFCE4.html b/articles/2025/Menu-editor-for-XFCE4.html new file mode 100755 index 0000000..8f2bedb --- /dev/null +++ b/articles/2025/Menu-editor-for-XFCE4.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Menu editor for XFCE4</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Menu editor for XFCE4</h2> +<h3>2025-03-23</h3> +<h4>Alacarte is a gui menu editor for Gnome and XFCE</h4> +<code>yay -S alacarte-xfce</code> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Microphone-distortion-on-Thinkpad-X220.html b/articles/2025/Microphone-distortion-on-Thinkpad-X220.html new file mode 100755 index 0000000..8b40778 --- /dev/null +++ b/articles/2025/Microphone-distortion-on-Thinkpad-X220.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Microphone distortion on Thinkpad X220</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Microphone distortion on Thinkpad X220</h2> +<h3>2025-04-27</h3> +<h4>Trying to fix a bad microphone</h4> +<p>The X220 has a notoriously bad internal microphone. It can be somewhat improved by making the input mono with module-remap-source<p> +<p><b>/etc/modules-load.d/microphone.conf</b></p> +<pre> +# make micro mono +module-remap-source master=alsa_input.pci-0000_00_1b.0.analog-stereo channels=1 master_channel_map=front-left channel_map=mono +</pre> +<p>Replace master= with your device, and master_channel_map= with the device's channel</p> +<p>It's a bit better, although an external usb microphone is the real solution.</p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Modified-dwm-bar-battery-script.html b/articles/2025/Modified-dwm-bar-battery-script.html new file mode 100755 index 0000000..b837c73 --- /dev/null +++ b/articles/2025/Modified-dwm-bar-battery-script.html @@ -0,0 +1,80 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Modified dwm-bar battery script</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Modified dwm-bar battery script</h2> +<h3>2025-04-23</h3> +<h4>adding battery charging nerdfont icons</h4> +<p>This is a modification of the default dwm-battery script</p> +<p><img src="/images/blog/dwm_bar.png"></p> +<pre> +#!/bin/sh + +# A dwm_bar function to read the battery level and status +# GNU GPLv3 + +dwm_battery () { + # Change BAT1 to whatever your battery is identified as. Typically BAT0 or BAT1 + CHARGE=$(cat /sys/class/power_supply/BAT0/capacity) + STATUS=$(cat /sys/class/power_supply/BAT0/status) + + #printf "%s" "$SEP1" + printf " %s" + if [ "$IDENTIFIER" = "unicode" ]; then + if [ "$STATUS" = "Charging" ]; then + if [ "${CHARGE}" -lt 10 ]; then ICON="" + elif [ "${CHARGE}" -lt 20 ]; then ICON="" + elif [ "${CHARGE}" -lt 30 ]; then ICON="" + elif [ "${CHARGE}" -lt 40 ]; then ICON="" + elif [ "${CHARGE}" -lt 50 ]; then ICON="" + elif [ "${CHARGE}" -lt 60 ]; then ICON="" + elif [ "${CHARGE}" -lt 70 ]; then ICON="" + elif [ "${CHARGE}" -lt 80 ]; then ICON="" + elif [ "${CHARGE}" -lt 90 ]; then ICON="" + elif [ "${CHARGE}" -lt 100 ]; then ICON="" + fi + else + if [ "${CHARGE}" -lt 10 ]; then ICON="" + elif [ "${CHARGE}" -lt 20 ]; then ICON="" + elif [ "${CHARGE}" -lt 30 ]; then ICON="" + elif [ "${CHARGE}" -lt 40 ]; then ICON="" + elif [ "${CHARGE}" -lt 50 ]; then ICON="" + elif [ "${CHARGE}" -lt 60 ]; then ICON="" + elif [ "${CHARGE}" -lt 70 ]; then ICON="" + elif [ "${CHARGE}" -lt 80 ]; then ICON="" + elif [ "${CHARGE}" -lt 90 ]; then ICON="" + elif [ "${CHARGE}" -lt 100 ]; then ICON="" + fi + fi + printf "%s %s%% " "$ICON" "$CHARGE" + else + printf "BAT %s%% %s" "$CHARGE" "$STATUS" + fi + #printf "%s\n" "$SEP2" +} + +dwm_battery +</pre> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/My-gopher-site.html b/articles/2025/My-gopher-site.html new file mode 100755 index 0000000..c372098 --- /dev/null +++ b/articles/2025/My-gopher-site.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>My gopher site</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>My gopher site</h2> +<h3>2025-09-26</h3> +<h4>moving to saner tech</h4> +<p><img src="/images/blog/mygophersite.png" alt="my gopher site pic"></p> +<p>If you haven't noticed, I'm blogging (phlogging) on my gopher site now.</p> +<p>If you have a termnal, install lynx and point it to <a href="gopher://wittamore.fr">gopher://wittamore.fr</a></p> +<p>If you don't, point your web browser to <a href="https://gopherproxy.meulie.net/wittamore.fr/">gopherproxy</a>, +the results are not as good but you'll get the gist.</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Naked-DWM.html b/articles/2025/Naked-DWM.html new file mode 100755 index 0000000..bb64c73 --- /dev/null +++ b/articles/2025/Naked-DWM.html @@ -0,0 +1,101 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Naked DWM</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Naked DWM</h2> +<h3>2025-05-01</h3> +<h4>Zero dwm patches...</h4> +<p>Inspiration from <a href="https://youtu.be/O5VVdFWJcak">Bread on Penguins</a> and <a href="https://www.youtube.com/watch?v=g8_d2rnwQdo&t=1206s">BugsWriter</a></p> +<p><img src="/images/blog/pywal16-screenshot.png"></p> +<p>I've been messing with DWM patches but I went back to naked DWM. +The only caveat is a couple of lines of code in <b>dwm/config.h</b> to integrate Pywal16, +that allows me to have a colour theme that matches whatever wallpaper I'm using.</p> +<p>It's possible to patch DWM with the Xresources patch but patches are version dependant and can be a pain. +I prefer simple and maintainable.</p> +<p>Install <a href="https://github.com/eylles/pywal16">pywal16</a></p> +<pre> +yay -S python-pywal16 +</pre> +<h4>Modify dwm/config.h</h4> +<p>In <b>dwm/config.h</b> I removed the default colour settings:</p> +<pre> +static const char col_gray1[] = "#222222"; +static const char col_gray2[] = "#444444"; +static const char col_gray3[] = "#bbbbbb"; +static const char col_gray4[] = "#eeeeee"; +static const char col_cyan[] = "#005577"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, +}; +</pre> +<p>and replaced them with this line:</p> +<pre> +/* Pywal16 */ +#include "/home/philip/.cache/wal/colors-wal-dwm.h" +</pre> +<h4>xinitrc</h4> +<p>In my .xinitrc I have <b>wal -Rq</b> which recalls the last wallpaper setting.</p> +<h4>Chooser script</h4> +<p>This script chooses the wallpaper then recompiles dwm and dmenu.</p> +<pre> +#!/usr/bin/env bash +# Choose a wallpaper +# requires nsxiv, pywal16 +# ~/.local/bin is in PATH + +FOLDER=~/Pictures/wallpaper +# thumbnail display of images +CHOICE=$(nsxiv -otb $FOLDER/*) + +wal -i $CHOICE + +# Post process + +# DWM +cd ~/.local/src/dwm +make clean ; make +rm -f ~/.local/bin/dwm +cp dwm ~/.local/bin + +# DWMMENU +rm -f ~/.local/dmenu* +cd ~/.local/src/dmenu +make clean ; make +cp {dmenu,dmenu_run,dmenu_path} ~/.local/bin +</pre> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> + + + + + + + + + + + diff --git a/articles/2025/Neomutt-as-default-in-XFCE4.html b/articles/2025/Neomutt-as-default-in-XFCE4.html new file mode 100755 index 0000000..4c7259b --- /dev/null +++ b/articles/2025/Neomutt-as-default-in-XFCE4.html @@ -0,0 +1,47 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Neomutt as default in XFCE4</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Neomutt as default in XFCE4</h2> +<h3>2025-03-21</h3> +<h4>I've always loved Mutt and now Neomutt on XFCE4</h4> +<p>Note: I use RBW to access my <b>Bitwarden</b> password vault on the command line or in +scripts, so I need to make sure it is unlocked before running Neomutt, hence +the Exec line in the desktop entry.</p> +<p><a href="https://github.com/doy/rbw">RBW</a> sudo pacman -S rbw</p> + +<h4>Create desktop entry</h4> + +<pre>~/.local/share/applications/neomutt.desktop</pre> + +<code>[Desktop Entry] +Categories=Office;Network;Email; +Comment=Simple text-based Mail User Agent +Exec=xfce4-terminal -x bash -c '"rbw unblock" ; neomutt' %u +Icon=neomutt +Name=neomutt +MimeType=x-scheme-handler/mailto; +NoDisplay=false +Terminal=false +Type=Application +</code> +<h4>Add xdg mime type</h4> +<pre>xdg-mime default neomutt.desktop x-scheme-handler/mailto</pre> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/New-builds-of-dwm-and-st.html b/articles/2025/New-builds-of-dwm-and-st.html new file mode 100755 index 0000000..1601b4c --- /dev/null +++ b/articles/2025/New-builds-of-dwm-and-st.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>New builds of dwm and st</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>New builds of dwm, dmenu, and st</h2> +<h3>2025-05-21</h3> +<h4>new builds with patches</h4> +<p>I've updated my builds of dwm, dmenu, and st, available on my <a href="https://git.wittamore.fr">git server</a></p> +<p>Although I've compiled St, Kitty and Ghostty are my favorite terminal emulators. St has many patches, but not all of them work together. St is meant to be a bare bones -suckless- terminal emulator.</p> +<p>The DWM build has the xrdb, rotatestack, adjacenttag, center, statuscmd and swallow patches.</p> +<p>I was using slstatus with time and battery indications, but dwmblocks-async adds bit of flair.</p> +<p>Dmenu has the xresources and center patches.</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Newsboat-and-gopher-RSS-feeds.html b/articles/2025/Newsboat-and-gopher-RSS-feeds.html new file mode 100755 index 0000000..ec1c48f --- /dev/null +++ b/articles/2025/Newsboat-and-gopher-RSS-feeds.html @@ -0,0 +1,36 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Newsboat and gopher RSS feeds</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Newsboat and gopher RSS feeds</h2> +<h3>2025-09-24</h3> +<h4>How to read gopher RSS feeds in newsboat</h4> +<p>curl is able to access gopher files, and newsboat has an "exec" command +that can be used run commands.</p> +<p>So to read a gopher rss feed you can simply edit .newsboat/urls and add the following line:</p> +<p>"exec:curl gopher://url"</p> +<p>For example:</p> +<p>"exec:curl gopher://lucio.albenga.es/0/lfa/en/phlog/rss.xml"</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Oddly-useful-Linux-tools.html b/articles/2025/Oddly-useful-Linux-tools.html new file mode 100755 index 0000000..719dd56 --- /dev/null +++ b/articles/2025/Oddly-useful-Linux-tools.html @@ -0,0 +1,50 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>Oddly useful Linux tools</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + + <main> + <article> + <h2>Oddly useful Linux tools</h2> + <h3>2025-05-08</h3> + <h4>Bread on Penguins with some interesting tools</h4> + <p> + Some really interesting videos, Bread on Penguins is taking up where + Luke Smith left off + </p> + <div class="wrapper"> + <iframe + src="https://www.youtube.com/embed/32O0a6qCQaQ" + title="Oddly useful Linux tools you probably haven't seen before" + frameborder="0" + allow=" + accelerometer; + autoplay; + clipboard-write; + encrypted-media; + gyroscope; + picture-in-picture; + web-share; + " + referrerpolicy="strict-origin-when-cross-origin" + allowfullscreen + ></iframe> + </div> + </article> + </main> + <footer> + <p> </p> + + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2025/Pipewire.html b/articles/2025/Pipewire.html new file mode 100755 index 0000000..dd75885 --- /dev/null +++ b/articles/2025/Pipewire.html @@ -0,0 +1,62 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Pipewire</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Pipewire</h2> +<h3>2025-04-28</h3> +<h4>Fixing Thinkpad X220 microphone woes</h4> +<p>So with my nice new DWM installation I waltz into a Jitsi meeting only to find that my microphone +wasn't working even though it appeared to. Something was missing in pulseaudio after I removed XFCE4, or +wasn't taken into account in DWM. Then I noticed that Librewolf was looking for Pipewire. What?</p> +<p>Eventually I got it working, but the microphone was distorted and full of parasites. +<a href="/articles/2025/Microphone-distortion-on-Thinkpad-X220.html">Adding a module remap</a> to +turn the two laptop microphones into mono was a tad better but I decided go for the bleeding +edge and install Pipewire instead of Pulseaudio.</p> +<pre> +sudo pacman -Rdd pulseaudio +sudo pacman -S pipewire-{jack,alsa,pulse} +systemctl --user enable --now pipewire pipewire-pulse pipewire-media-session +</pre> +<h4>Noise suppression</h4> +<pre> +git clone https://github.com/werman/noise-suppression-for-voice.git +</pre> +<p>Follow the instructions at <a href="https://github.com/werman/noise-suppression-for-voice">Noise suppression for Voice</a></p> +<p>In order to change the default source:</p> +<pre> +pactl list sources short +pactl set-default-source rnnoise_source +systemctl --user restart pipewire.service +</pre> +<p>And that's the best I'll probably ever get out of the internal X220 microphones.</p> +<p>Why not just use Alsa? Because I use a bluetooth headset and noise suppression.</p> +<p>Why not just use an external microphone? I've ordered one :-)</p> + +<h3>Bluetooth Autoconnect</h3> +<p>Just to add, Bluetooth Autoconnect was built for Pulseaudio but works fine with Pipewire.</p> +<p>Bluetooth autoconnect will connect any client that is paired and trusted, in my case a headset.</p> +<p>If you use Chaotic-Aur, pacman -S bluetooth-autoconnect will install it. Use yay -S bluetooth-autoconnect if not.</p> + +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Power-notifications.html b/articles/2025/Power-notifications.html new file mode 100755 index 0000000..af66de7 --- /dev/null +++ b/articles/2025/Power-notifications.html @@ -0,0 +1,31 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Power notifications</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Power notifications</h2> +<h3>2025-04-26</h3> +<h4>Searching how to get a notification for power source changes or USB drives</h4> +<p>I found the solution here: <a href="https://github.com/pliski/udev_desktop_notifications">udev desktop notifications</a></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Powerline-Bash.html b/articles/2025/Powerline-Bash.html new file mode 100755 index 0000000..9af5c90 --- /dev/null +++ b/articles/2025/Powerline-Bash.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Powerline Bash</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Powerline Bash</h2> +<h3>2025-03-24</h3> +<h4>Make your bash prompt look real pretty</h4> +<p><img src="/images/blog/powerline_bash.png"></p> +<p><a href="https://gitlab.com/bersace/powerline.bash">powerline.bash</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/RSS-feeds-that-don't-suck-on-terminals.html b/articles/2025/RSS-feeds-that-don't-suck-on-terminals.html new file mode 100755 index 0000000..d3757bf --- /dev/null +++ b/articles/2025/RSS-feeds-that-don't-suck-on-terminals.html @@ -0,0 +1,83 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>RSS feeds that don't suck on terminals</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>RSS feeds that don't suck on terminals</h2> +<h3>2025-04-05</h3> +<h4>RSS news in the terminal</h4> +<p>I'm building a list of RSS feeds that work in the terminal. +There are a lot of feeds out there but not all of the linked articles display well.</p> +<ul> +<li>I'm using the Newsboat rss reader</li> +<li>I'm using the <a href="/articles/2025/Ghostty.html">Ghostty terminal</a></li> +<li>I'm using the <a href="https://sr.ht/~bptato/chawan/">Chawan browser</a></li> +</ul> +<p>Basically it's a question of whether the page linked in the RSS feed is displayed well in +a text browser, which isn't always the case. I find the Chawan text browser gives the best +web display in general and includes images, so I'm using it as a benchmark.</p> +<p>It's always possible to use rdrview to pre-format the text before sending to cha, +with <b>browser "rdrview -B cha %u"</b> in <b>.newsboat/config</b>, but this removes the images.</p> +<p>This is an ongoing task so it will probably end up as a static page</p> +<h3>The list so far:</h3> +<code> +---Linux--- +https://old.reddit.com/r/commandline/.rss +https://old.reddit.com/r/Ghostty/.rss +https://old.reddit.com/r/xfce/.rss +https://old.reddit.com/r/neomutt/.rss +https://old.reddit.com/.rss +https://itsfoss.com/rss/ +https://archlinux.org/feeds/news +https://www.linuxjournal.com/node/feed +https://www.cyberciti.biz/feed/ +https://terminaltrove.com/blog.xml +https://terminaltrove.com/new.xml +https://terminaltrove.com/totw.xml + +---News-English--- +https://www.theatlantic.com/feed/all/ + +---News-French--- +https://www.thelocal.fr/feeds/rss.php +https://www.diplomatie.gouv.fr/spip.php?page=backend-fd +https://mondediplo.com/backend +https://frenchdailynews.com/feed +https://www.euronews.com/rss +https://tempsreel.nouvelobs.com/rss.xml +https://www.lepoint.fr/rss.xml + +---Science--- +https://www.science.org/rss/news_current.xml +http://rss.sciam.com/ScientificAmerican-Global +https://www.lemonde.fr/sciences/rss_full.xml +https://www.newscientist.com/feed/home/?cmpid=RSS%7CNSNS-Home +http://feeds.feedburner.com/sciencealert-latestnews +http://awesci.com/feed/ +https://blogs.esa.int/rocketscience/feed/ + +---Tech--- +https://feeds.feedburner.com/TheHackersNews?format=xml + +---Divertissement--- +https://xkcd.com/rss.xml + +---Other--- +</code> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Reducing-web-font-size.html b/articles/2025/Reducing-web-font-size.html new file mode 100755 index 0000000..e03b92f --- /dev/null +++ b/articles/2025/Reducing-web-font-size.html @@ -0,0 +1,39 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Reducing web font size</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Reducing web font size</h2> +<h3>2025-04-06</h3> +<h4>I stumbled on a marvellous post</h4> +<p>I started using the Jetbrains font for this blog, but adding the font in the css file makes it +pretty heavy, and I've been considering removing it. BUT...</p> +<p>I stumbled on <a href="https://neilzone.co.uk/2024/05/faffing-with-fonts-to-reduce-my-web-page-size-by-two-thirds/">this post</a> +and tried Neil's suggestion on the JetbrainsMono-Regular truetype font, and the font filesize was reduced from 274k to 66Kb</p> +<code>pipx install fonttools +pyftsubset JetbrainsMono-Regular.ttf --output-file=JetbrainsMono-Regular-reduced.ttf --unicodes=U+0020-007E</code> +<p>So I decided to try it on the JetBrainsMono Regular Nerdfont, a whopping 2.4Mb, but the result was even better, 18Kb!</p> +<code>.rw-r--r-- 66k philip 6 avril 23:04 JetBrainsMono-reduced.ttf +.rw-r--r-- 18k philip 6 avril 23:20 JetBrainsMonoNerd-reduced.ttf +</code> +<p>Understanding why will take a bit of forensics, in the mean time this blog has become much lighter.</p> +<h3>Update</h3> +<p>I wound up just using the default monospace for the main text, and Ultra (reduced) for the title .</p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Replace-sudo-with-doas-on-Artix.html b/articles/2025/Replace-sudo-with-doas-on-Artix.html new file mode 100755 index 0000000..d05bb5a --- /dev/null +++ b/articles/2025/Replace-sudo-with-doas-on-Artix.html @@ -0,0 +1,92 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Replace sudo with doas on Artix</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Replace sudo with doas on Artix</h2> +<h3>2025-06-18</h3> +<h4>More efficient, less complicated</h4> +<p>Sudo is quite bloated and not without security problems. It is intended to cover situations +that normal Linux users will never encounter, and its configuration is messy.</p> +<p>Doas is an application from OpenBSD that is simpler and cleaner.</p> +<p>Here are the steps I took to install doas and replace sudo:</p> +<code>sudo pacman -S opendoas</code> +<p>Add my username to the group wheel:</p> +<code>sudo usermod -aG wheel philip</code> +<p>Create /etc/doas.conf and add:</p> +<code>permit setenv {PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin} :wheel</code> +<p>Modify the ownership and permissions:</p> +<code>doas chown -c root:root /etc/doas.conf +doas chmod -c 0400 /etc/doas.conf +</code> +<p>You should now be able to remove sudo:</p> +<code>doas pacman -Rdd sudo</code> +<p>Create a symlink to sudo</p> +<code>ln -s $(which doas) /usr/bin/sudo</code> +<p>So now it would be nice to have the equivalent of sudoedit.</p> +<p>Create /root/scripts/doasedit (I use the micro editor):</p> +<code>#!/usr/bin/env bash + +DOASDIR="/tmp/doas-$(date +%s)" +mkdir $DOASDIR +chmod 700 $DOASDIR +DOASFILE="$DOASDIR/doas.conf" + +cp /etc/doas.conf $DOASFILE +chmod 600 $DOASFILE + +micro $DOASFILE +sync + +doas -C $DOASFILE && echo "valid config" && cp $DOASFILE /etc/doas.conf && chmod 400 /etc/doas.conf || echo "invalid config" +sync + +rm -rf $DOASDIR +</code> + +<p>Create /usr/local/bin/doasedit:</p> + +<code>#!/usr/bin/env bash +if [ "$(id -u)" != 0 ]; then + doas /root/script/doasedit +else + /root/script/doasedit +fi +</code> + +<p>Modify the permissions</p> +<code>doas chmod 700 /root/script/doasedit +doas chmod 755 /usr/local/bin/doasedit +</code> + +<p>Test doasedit:</p> +<code>doasedit</code> +<p>add this line:</p> +<code>permit nopass philip as root cmd pacman</code> +<p>save and you should see:</p> +<code>valid config</code> + +<p><a href="https://www.cjjackson.dev/posts/replacing-sudo-with-doas-on-arch-linux/">Source</a></p> + + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Rsync-backup-to-Synology-Diskstation.html b/articles/2025/Rsync-backup-to-Synology-Diskstation.html new file mode 100755 index 0000000..62f9c50 --- /dev/null +++ b/articles/2025/Rsync-backup-to-Synology-Diskstation.html @@ -0,0 +1,46 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Rsync backup to Synology Diskstation</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Rsync backup to Synology Diskstation</h2> +<h3>2025-03-25</h3> +<h4>This could change but it works for me.</h4> +<p>My Diskstation isn't always on, so I run this from the command line rather than cron.</p> +<p>To avoid errors devices and links are skipped, permissions, times, group and owner are not written and files are compared by size only.</p> +<p>Ssh is configured with ssh-copy-id for passwordless access and host:port details in .ssh/config</p> +<h3>Note: I set the rsync options on the diskstation but had to chmod 755 my +diskstation's home folder before passwordless ssh worked</h3> +<code>#!/bin/sh + + rsync -r --no-D --no-t --no-o --no-g --no-p \ + --size-only \ + --delete \ + --update \ + --exclude .cache/ \ + --exclude Downloads/ \ + --exclude tmp/ \ + --exclude .local/share/Trash/ \ + --exclude .wine \ + --info=progress2 \ + --stats \ + /home/philip philip@dsm:backup-X220 + +</code> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Screen-scaling-with-xrandr.html b/articles/2025/Screen-scaling-with-xrandr.html new file mode 100755 index 0000000..9e42c67 --- /dev/null +++ b/articles/2025/Screen-scaling-with-xrandr.html @@ -0,0 +1,44 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Screen scaling with xrandr</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Screen scaling with xrandr</h2> +<h3>2025-03-30</h3> +<h4>Changing screen scaling on my X220</h4> +<p>The Thinkpad X220 has a screen resolution of 1366x768 (16:9), which suits the size of the 13" screen, +but sometimes you stumble on applications that have gui elements that are taller than the screen.</p> +<p>In Linux, it's possible to change the screen scaling to emulate a larger screen with xrandr. +It won't look as crisp as the real resolution, but might get you out of a bind.</p> +<p>This command will emulate a 1920x1080 (16:9) screen:</p> +<code>xrandr --output LVDS1 --panning 1920x1080 --scale 1.406x1.406</code> +<p>or 1600x900:</p> +<code>xrandr --output LVDS1 --panning 1600x900 --scale 1.171x1.172</code> +<p>To return to the original scaling, type:</p> +<code>xrandr --output LVDS1 --panning 1366x768 --scale 1x1</code> +<p>On the other hand , if you have for example a 1920x1080 (16:9) screen but would like to +scale down to 1600x900<br>(which might well be the case when I receive the new 1920x1080 IPS screen for my 14" T430)</p> +<code>xrandr --output LVDS1 --panning 1600x900 --scale 0.8333x0.8333</code> +<p> </p> +<p>In XFCE4, you can also magnify the resolution in <br> +<b>Settings->Appearance->Settings->Window Scaling</b><br></p> +<p>you can also do this on the command line with</p> +<code>xfconf-query -c xsettings -p /Gdk/WindowScalingFactor -s SCALE</code> +<p>where SCALE is either 1 or 2</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Shellcheck-your-bash-scripts.html b/articles/2025/Shellcheck-your-bash-scripts.html new file mode 100755 index 0000000..36ff5c7 --- /dev/null +++ b/articles/2025/Shellcheck-your-bash-scripts.html @@ -0,0 +1,36 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>Shellcheck your bash scripts</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + <hr /> + </header> + + <main> +<article> +<h2>Shellcheck your bash scripts</h2> + <h3>2025-05-14</h3> + <h4>Shellcheck is a handy online and command line tool</h4> + <p> + I've started using this tool to clean up my scripts. Testing via the web + interface displays links to explanations of the errors or warnings found. + </p> + <p><a href="https://www.shellcheck.net/">https://www.shellcheck.net/</a></p> + <pre>yay -Ss shellcheck</pre> + </article> +</main> +<footer> + + <p> </p> + <hr /> + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Simplex-a-decentralised-private-messenger.html b/articles/2025/Simplex-a-decentralised-private-messenger.html new file mode 100755 index 0000000..11f6efe --- /dev/null +++ b/articles/2025/Simplex-a-decentralised-private-messenger.html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Simplex a decentralised private messenger</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Simplex a decentralised private messenger</h2> +<h3>2025-04-04</h3> +<h4>A messenger app without user IDs</h4> +<p>Other apps have user IDs: Signal, Matrix, Session, Briar, Jami, Cwtch, etc. +SimpleX does not, not even random numbers.This radically improves your privacy.</p> +<p><a href="https://simplex.chat/">Simplex</a></p> +<p><a href="https://rickcarlino.com/2024/simplex-chat-bots.html">Rick Carlino's blog</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Slim-fork-login-manager.html b/articles/2025/Slim-fork-login-manager.html new file mode 100755 index 0000000..1b55788 --- /dev/null +++ b/articles/2025/Slim-fork-login-manager.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Slim-fork login manager</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Slim-fork login manager</h2> +<h3>2025-04-26</h3> +<h4>Slim has been ressurected</h4> +<p>Slim is an lightweight login manager that has been ressurected, patched, and improved.</p> +<p>Oh the power of open source software...</p> +<p><a href="https://slim-fork.sourceforge.io/">Slim fork</a></p> +<p><a href="https://github.com/adi1090x/slim_themes">Slim Themes</a></p> +<p><img src="/images/blog/slim-theme.png" alt="slim greeny-dark theme screenshot"></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Slstatus-date-with-alternative-locale.html b/articles/2025/Slstatus-date-with-alternative-locale.html new file mode 100755 index 0000000..60d7b44 --- /dev/null +++ b/articles/2025/Slstatus-date-with-alternative-locale.html @@ -0,0 +1,59 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Slstatus date with alternative locale</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Slstatus date with alternative locale</h2> +<h3>2025-04-26</h3> +<h4>Modifying slstatus to display French date</h4> +<p>My default locale is en-US, but I wanted to display weekdays in French in the slstatus bar.</p> +<p>The solution is to add setlocale to the function in <br><b>slstatus/components/datetime.c</b></p> +<p><img src="/images/blog/slstatus-french.png"></p> +<pre> +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <time.h> +<b>#include <locale.h></b> + +#include "../slstatus.h" +#include "../util.h" + + +const char * +datetime(const char *fmt) +{ + <b>setlocale(LC_ALL,"fr_FR.utf8");</b> + time_t t; + + t = time(NULL); + + if (!strftime(buf, sizeof(buf), fmt, localtime(&t))) { + warn("strftime: Result string exceeds buffer size"); + return NULL; + } + + return buf; +} +</pre> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Starship-prompt.html b/articles/2025/Starship-prompt.html new file mode 100755 index 0000000..5371de9 --- /dev/null +++ b/articles/2025/Starship-prompt.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Starship prompt</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Starship prompt</h2> +<h3>2025-04-26</h3> +<h4>Making the terminal pretty with Starship</h4> +<p>I got fed up with the powerline prompt and installed <a href="https://starship.rs/">starship</a> +with the <a href="https://starship.rs/presets/gruvbox-rainbow">gruvbox-rainbow</a> preset.</p> +<p><img src="/images/blog/starship-gruvbox-rainbow.png"></p> +<p>And with that, I'm done for the day.</p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Stick-it-to-the-man.html b/articles/2025/Stick-it-to-the-man.html new file mode 100755 index 0000000..2846439 --- /dev/null +++ b/articles/2025/Stick-it-to-the-man.html @@ -0,0 +1,36 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Stick it to the man</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Artix: Stick it to the man</h2> +<h3>2025-06-29</h3> +<h4>Artix is the resistance: Xlibre</h4> +<p><img width="25%" src="/images/blog/resistance.jpg" alt="resistance.jpg"></p> +<p><a href="https://linux.slashdot.org/story/25/06/28/054245/x11-fork-xlibre-released-for-testing-on-systemd-free-artix-linux">Xlibre released for testing on Artix Linux</a></p> +<p>Artix is becoming what Arch should have been. While corporations have been instrumental in Linux's progression over the years, +allowing them to forge Linux into a new sort of Windows where applications are becoming totally phagocyted by a +few pieces of software (Wayland, Systemd,...) requires solid resistance.</p> +<p>Kudos to the people making this happen.</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/TLP-recalibration-error-on-Thinkpad-X220.html b/articles/2025/TLP-recalibration-error-on-Thinkpad-X220.html new file mode 100755 index 0000000..6ba27a4 --- /dev/null +++ b/articles/2025/TLP-recalibration-error-on-Thinkpad-X220.html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>TLP recalibration error on Thinkpad X220</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>TLP recalibration error on Thinkpad X220</h2> +<h3>2025-04-08</h3> +<h4>Error: cannot read charge level of battery BAT0</h4> +<p>My attempt to recalibrate the battery on my X220 failed. Hunting for info I found <a href="https://github.com/linrunner/TLP/issues/793">this on github</a> +<p>It's a TLP bug that was fixed a couple of weeks ago. The solution was to install the git versions of tlp.</p> +<code>yay -S tlp-git tlp-rdw-git</code> +<p>Now I can plug in my X220 to the mains and run tlp recalibrate BAT0 overnight.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Taking-the-leap-to-Artix.html b/articles/2025/Taking-the-leap-to-Artix.html new file mode 100755 index 0000000..6daee3b --- /dev/null +++ b/articles/2025/Taking-the-leap-to-Artix.html @@ -0,0 +1,45 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Taking the leap to Artix</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Taking the leap to Artix</h2> +<h3>2025-06-14</h3> +<h4>systemd no more</h4> +<p>Artix Linux is a distribution based on Arch, but without systemd.</p> +<p>Seeing how systemd has become insidious within Linux it's no mean feat.</p> +<p>After watching <a href="https://youtu.be/ZsCiqogu-bg">this video</a>, +and having read a lot of systemd warnings by old timers, +I decided to try Artix. I prefer a rolling release distribution, and I like Arch a lot, +so Artix was the logical choice.</p> +<p>After the first hour of floundering around I got it working.</p> +<p>For some reasong the default network manager was limiting network access, for example the Artix site +and my own site were ok, but reddit and duckduckgo were not allowed. I checked /etc/hosts but couldn't find +the culprit. In the end I removed ConnMan and installed networkmanager and it worked straight away. In addition +I can now use nmtui.</p> +<p>There are still things to fix, for example the SLiM desktop manager no longer works, true it's ancient, +so I'll have to find a replacement Until then I'm just typing startx after logging in.</p> +<p>I have dwm running, bluetooth and pipewire too, so it's just a question of fiddling around.</p> + + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Terminal-Trove.html b/articles/2025/Terminal-Trove.html new file mode 100755 index 0000000..e07b7ea --- /dev/null +++ b/articles/2025/Terminal-Trove.html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Terminal Trove</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Terminal Trove</h2> +<h3>2025-04-02</h3> +<h4>Terminal Trove curates and showcases all things in the terminal</h4> +<p>such as command line interface tools (CLI), text mode interface tools (TUI), developer tools and more no matter what platform or medium.</p> +<p>The site also has RSS feeds, and is present on mastodon and BlueSky among others</p> +<p><a href="https://terminaltrove.com/">https://terminaltrove.com</a></p> +<p>Nice site</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/The-Mote-in-God's-eye.html b/articles/2025/The-Mote-in-God's-eye.html new file mode 100755 index 0000000..d6c779c --- /dev/null +++ b/articles/2025/The-Mote-in-God's-eye.html @@ -0,0 +1,36 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>The Mote in God's eye</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>The Mote in God's eye</h2> +<h3>2025-04-06</h3> +<h4>Science fiction by Larry Niven and Jerry Pournelle</h4> +<p>I'm re-reading this book on my Kobo. It's a book I first read back in the 70's. +I probably bought it at Shakespeare & Co. a famous bookshop in the latin quarter of Paris, where +I could go and spend a couple of hours reading a book in a big leather armchair, +so it bears a touch of nostalgia for me.</p> +<p>The premise is of an intelligent species (the Moties), locked in it's own solar system, +victim of endless cycles of expansion and destruction due to it's fertility. +Larry Niven thought big when he wrote novels like Ringworld, and here it's no exception.</p> +<p>Some things seem a bit dated, for example the descendents of Russia, America or Scotland +being important in the distant future, but all in all a good thought provoking read. +To me it begs the question: what if our own species winds up becoming Moties?</p> +<p>📘 <a href="https://www.kobo.com/us/en/ebook/the-mote-in-god-s-eye">Kobo ebook</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/The-last-thing-the-EU-needs-....html b/articles/2025/The-last-thing-the-EU-needs-....html new file mode 100755 index 0000000..05dd1d6 --- /dev/null +++ b/articles/2025/The-last-thing-the-EU-needs-....html @@ -0,0 +1,31 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>The last thing the EU needs ...</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>The last thing the EU needs ...</h2> +<h3>2025-04-09</h3> +<h4>Clickbait title for some serious points</h4> +<p><a href="https://fossforce.com/2025/04/the-last-thing-the-eu-needs-is-its-own-linux-distro/">The Last Thing the EU Needs Is Its Own Linux Distro</a></p> +<p>Having installed Linux in a couple of schools only to have the teachers showered in Microsoft freebies and propaganda, +I fully support this guy because otherwise I know how this is going to end up.</p> +<p>The roll over and beg attitude of authorities towards Microsoft lobbying is terrifying, and it has to change through top down +directives that place independant European solutions first.</p> + +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/The-universe-is-spinning.html b/articles/2025/The-universe-is-spinning.html new file mode 100755 index 0000000..c537ffc --- /dev/null +++ b/articles/2025/The-universe-is-spinning.html @@ -0,0 +1,55 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>The universe is spinning</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + + <main> + <article> + <h2>The universe is spinning</h2> + <h3>2025-04-13</h3> + <h4>What goes around, comes around</h4> + <p> + One of my favorite Youtubers on the subject of our spinning universe + </p> + <p>No wonder I get vertigo.</p> + <div class="wrapper"> + <iframe + src="https://www.youtube.com/embed/Gm5nvLJoXCI" + title="The Entire Universe Seems to Spin, New Data Reveal" + frameborder="0" + allow=" + accelerometer; + autoplay; + clipboard-write; + encrypted-media; + gyroscope; + picture-in-picture; + web-share; + " + referrerpolicy="strict-origin-when-cross-origin" + allowfullscreen + ></iframe> + </div> + <p> + <a href="https://www.youtube.com/watch?v=Gm5nvLJoXCI" + >Sabine Hossenfelder</a + > + </p> + </article> + </main> + <footer> + <p> </p> + + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2025/Thinkpad-T430-screen-upgrade.html b/articles/2025/Thinkpad-T430-screen-upgrade.html new file mode 100755 index 0000000..aa4fcdb --- /dev/null +++ b/articles/2025/Thinkpad-T430-screen-upgrade.html @@ -0,0 +1,33 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Thinkpad T430 screen upgrade</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Thinkpad T430 screen upgrade</h2> +<h3>2025-03-21</h3> +<h4>My T430's current screen is 1368x768, a bit crappy</h4> +<p>So I've decided to upgrade it.</p> +<p>I've purchased a B140HAN01.3 screen and an LCD controller board kit from Aliexpress</p> +<p>I'll update this article when it arrives</p> +<p><a href="https://www.aliexpress.com/item/32836668337.html">Controller board</a></p> +<p><a href="https://www.aliexpress.com/item/1005004341552184.html">1920x1080 IPS screen</a></p> +<h3>Oh poo.</h4> +<p>The parts arrived, but when I installed them only the backlight works. I tried another LCD controller kit +but there's no display. I hit a wall with the screen supplier who won't replace it. Lesson learned.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Thinkpad-X220-Fan-control.html b/articles/2025/Thinkpad-X220-Fan-control.html new file mode 100755 index 0000000..2e8010d --- /dev/null +++ b/articles/2025/Thinkpad-X220-Fan-control.html @@ -0,0 +1,30 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Thinkpad X220 Fan control</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Thinkpad X220 Fan control</h2> +<h3>2025-03-21</h3> +<h4>Blowing out the heat from my laptop</h4> +<p>After installing thinkfan, <code>sudo pacman -S thinkfan</code> +<p><code>journalctl -p 3 -xb</code> was showing an error concerning thinkfan, and I worked out that the thinkfan_acpi module wasn't being loaded on boot.</p> +<p>Adding thinkpad_acpi to <code>/etc/modules-load.d/thinkpad.conf</code> didn't work</p> +<p>What did work was editing <code>/etc/default/grub</code> and adding <code>thinkpad_acpi.fan_control=1</code> to <code>GRUB_CMDLINE_LINUX_DEFAULT</code></p> +<p>Then </p><p><code>sudo grub-mkconfig -o /boot/grub/grub.cfg</code></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Thinkvantage-button-in-DWM.html b/articles/2025/Thinkvantage-button-in-DWM.html new file mode 100755 index 0000000..5721c8e --- /dev/null +++ b/articles/2025/Thinkvantage-button-in-DWM.html @@ -0,0 +1,41 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Thinkvantage button in DWM</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Thinkvantage button in DWM</h2> +<h3>2025-04-27</h3> +<h4>Putting the Thinkvantage button to use</h4> +<p><img src="/images/blog/thinkvantage.png" alt="thinkvantage button"></p> +<p>On my Thinkpad X220 there's a blue Thinkvantage button that only gets used when the machine boots to select different options.</p> +<p>Using xev the button is referenced as XF86XK_Launch1, here I use it to run a rofi menu for logging out, shutting down, rebooting, etc.</p> +<p><b>dwm/config.h</b></p> +<pre> +... +{ 0, XF86XK_Launch1, spawn, SHCMD("rofi -show power-menu -modi power-menu:rofi-power-menu") }, +... +</pre> +<p>power-menu <a href="https://github.com/jluttine/rofi-power-menu">can be found here</a></p> +<p><img src="/images/blog/powermenu.png"></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Thoughts-on-suckless.html b/articles/2025/Thoughts-on-suckless.html new file mode 100755 index 0000000..25d9cc8 --- /dev/null +++ b/articles/2025/Thoughts-on-suckless.html @@ -0,0 +1,46 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Thoughts on suckless</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Thoughts on suckless</h2> +<h3>2025-06-12</h3> +<h4>Note: opinions can change</h4> +<p>I've been playing with <a href="https://suckless.org/">suckless tools</a>: dwm, dmenu, st, surf</p> +<p>Dwm and dmenu are fantastic, st and surf not so much.</p> +<p>Both st and surf have patches that are incompatible, and +often outdated. The user is expected to know how to correct the patch +rejections, which is often possible, but knowledge of C is your best bet.</p> +<p>Suckless software is intended to be somewhat elite. Their web site +is frankly not suckless, perhaps intentionally? Some of the patches proposed +address problems that have already been solved in the main program.</p> +<p>Surf compiles but with GTK warnings about obsolete functions.</p> +<p>St is a headbanger if you want kittygraphics, boxdraw, and ligatures for example.</p> +<p>The educational aspect of suckless software is of course is invaluable, +and if it's the objective, then that's fine. The problem arises when you want to +build something that will fulfill your needs.</p> +<p>Because of all this I use Kitty or Ghostty instead of st, and Librewolf +instead of Surf. I just hope dwm doesn't fall into the same hole of wanting to +be elite to the point of not fulfilling it's task, which would be a terrible shame.</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Thoughts-on-zsh.html b/articles/2025/Thoughts-on-zsh.html new file mode 100755 index 0000000..b0d1af6 --- /dev/null +++ b/articles/2025/Thoughts-on-zsh.html @@ -0,0 +1,40 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Thoughts on zsh</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Thoughts on zsh</h2> +<h3>2025-06-22</h3> +<h4>do I need it?</h4> +<p>I kinda figured out that although youtubers attempt to make you feel a has-been if +you don't use all the bells and whistles that come with zsh, that Bash does everything I need, and I can extend it if needed.</p> +<p>Minimalism might be my aim, but it's not a do of die mantra, even so zsh appears rather flash and with esoteric possibilies that I will never use.</p> +<p>I'm also keeping my bash scripts, there seems to be little point in making them reliant on some other shell.</p> +<p>There are also a number of things that can improve your bash "experience" like Blesh - <a href="https://github.com/akinomyoga/ble.sh">Bash Line Editor</a></p> +<p>So I'm sticking to Bash for now.</p> +<p>Edit: after adding blesh I found that Midnight Commander (mc) took a few seconds to start, I fixed it with an alias:</p> +<code>alias mc="mc --nosubshell"</code> +<p>Re-Edit: sometimes typing in the termnal was non responsive, I got annoyed after a while and removed Blesh from .bashrc</p> +<p>Turns out simple is best, who would have thought it?</p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Toggle-screen-resolution-in-DWM.html b/articles/2025/Toggle-screen-resolution-in-DWM.html new file mode 100755 index 0000000..6928414 --- /dev/null +++ b/articles/2025/Toggle-screen-resolution-in-DWM.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Toggle screen resolution in DWM</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Toggle screen resolution in DWM</h2> +<h3>2025-06-28</h3> +<h4>using X220 keys</h4> +<p>Sometimes a badly written application will display a panel that is bigger than my X220's screen resolution, which is 1366x768.</p> +<p>To quickly solve this I've affected the Scroll Lock key to a script that toggles scaling between 1366x768 and 1600x900</p> +<p>The scaling adds some fuzziness but gets the job done.</p> +<p><a href="https://git.wittamore.fr/dwm/about/">My DWM fork</a></p> +<p><a href="https://git.wittamore.fr/scripts/tree/dwm-scale">DWM Scale script</a></p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Touchpad-deactivation.html b/articles/2025/Touchpad-deactivation.html new file mode 100755 index 0000000..2f66a78 --- /dev/null +++ b/articles/2025/Touchpad-deactivation.html @@ -0,0 +1,58 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Touchpad deactivation</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Touchpad deactivation</h2> +<h3>2025-06-09</h3> +<h4>When you have big hands and a little keyboard</h4> +<p>Until now I was using <b>synclient TouchpadOff=1</b> in my .xinitrc, but I was getting odd +results when using the keyboard mouse buttons. It turns out oddly the lower left area of my touchpad was infrequently active +and messing things up.</p> +<p>Replacing synclient with <b>xinput set-prop 11 172 0</b> fixed it.</p> +<p>I obtained the touchpad ID with <b>xinput list</b> and the command with <b>xinput -list-props 11</b></p> +<pre> + $ xinput list + ⎡ Virtual core pointer id=2 [master pointer (3)] + ⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)] + ⎜ ↳ Logitech Wireless Mouse id=9 [slave pointer (2)] + ⎜ ↳ TPPS/2 IBM TrackPoint id=12 [slave pointer (2)] + ⎣ Virtual core keyboard id=3 [master keyboard (2)] + ↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)] + ↳ Power Button id=6 [slave keyboard (3)] + ↳ Video Bus id=7 [slave keyboard (3)] + ↳ Sleep Button id=8 [slave keyboard (3)] + ↳ AT Translated Set 2 keyboard id=10 [slave keyboard (3)] + ↳ ThinkPad Extra Buttons id=13 [slave keyboard (3)] + ↳ Logitech Wireless Mouse id=14 [slave keyboard (3)] + ∼ SynPS/2 Synaptics TouchPad id=11 [floating slave] + +$ xinput -list-props 11 +Device 'SynPS/2 Synaptics TouchPad': + Device Enabled (172): 1 + ... + +$ xinput set-prop 11 172 0 + </pre> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Traditional-screenshot-of-my-X220.html b/articles/2025/Traditional-screenshot-of-my-X220.html new file mode 100755 index 0000000..19cb8af --- /dev/null +++ b/articles/2025/Traditional-screenshot-of-my-X220.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Traditional screenshot of my X220</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Traditional screenshot of my X220</h2> +<h3>2025-04-08</h3> +<h4>Thinkpad X22O running Arch Linux</h4> +<img src="/images/blog/Screenshot_2025-04-08_14-20-40.png" alt="screenshot of X220"> +<p>It's traditional to show off one's Thinkpad screenshot basking in all it's Linuxian glory, so here we are.</p> +<blockquote>My name is Ozymandias, King of Kings: Look on my works, ye Mighty, and despair!</blockquote> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Try-different-versions-of-Linux-online.html b/articles/2025/Try-different-versions-of-Linux-online.html new file mode 100755 index 0000000..eb6e25d --- /dev/null +++ b/articles/2025/Try-different-versions-of-Linux-online.html @@ -0,0 +1,33 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Try different versions of Linux online</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Try different distributions of Linux online</h2> +<h3>2025-07-14</h3> +<h4>Distrosea</h4> +<p>Here's a way of testing various distributions of Linux using your browser</p> +<p>Artix Linux is included</p> +<p><a href="https://distrosea.com">https://distrosea.com</a></p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Updated-cgit-server.html b/articles/2025/Updated-cgit-server.html new file mode 100755 index 0000000..53ce0d7 --- /dev/null +++ b/articles/2025/Updated-cgit-server.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Updated cgit server</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Updated cgit server</h2> +<h3>2025-05-28</h3> +<h4>dwm, dmenu, st repositories updated</h4> +<p>I've updated my git server with patched versions of DWM, Dmenu and ST</p> +<p>The cgit server is also fixed, I didn't realise that https cloning wasn't working.</p> +<p>It works now, for example:</p> +<pre>git clone https://git.wittamore.fr/dwm</pre> +<p><a href="https://git.wittamore.fr">Link to my git server</a><p> + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Upload-modified-blog-files-with-scp.html b/articles/2025/Upload-modified-blog-files-with-scp.html new file mode 100755 index 0000000..9b28eed --- /dev/null +++ b/articles/2025/Upload-modified-blog-files-with-scp.html @@ -0,0 +1,65 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Upload modified blog files with scp</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Upload modified blog files with scp</h2> +<h3>2025-03-29</h3> +<h4>A simple bash script to update this blog using scp</h4> +<h3>Note</h3> +<p>I have my web server aliased in ~/.ssh/config as "web" <br>so the remote folder is web:/folder</p> +<code>#!/bin/sh + +# blogsend : scp blog files to web server +# usage : <b>blogsend</b> <i>hours</i> (if not set default is 1 hour) +# example : <b>blogsend 72</b> sends files modified over the past 3 days + +# folders +rf= <i>#add your remote ssh folder here</i> +mf= <i>#add your local folder here</i> +cd $mf + +# test if option is sent +if [[ -z "$1" ]]; then + HOURS=1; +else + # test if option is a number + if [[ "$1" =~ ^[0-9]+$ ]]; then + HOURS=$1; + else + HOURS=1; + fi +fi + +MINUTES=$(($HOURS*60)) + +echo "Sending files modified in the past $HOURS hour(s)..." + +files=$(find * -path .tmp -prune -o -mmin -$MINUTES -type f) +for file in $files +do + echo -ne "-> "; + scp -r "$file" "$rf/$file"; +done + +echo "Done." + +exit 0 +</code> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Using-Goaccess-to-view-web-stats.html b/articles/2025/Using-Goaccess-to-view-web-stats.html new file mode 100755 index 0000000..8312fa5 --- /dev/null +++ b/articles/2025/Using-Goaccess-to-view-web-stats.html @@ -0,0 +1,32 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Using Goaccess to view web stats</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Using Goaccess to view web stats</h2> +<h3>2025-04-03</h3> +<h4>Using Goaccess locally</h4> +<p>I have goaccess installed on my laptop to view the web stats on a vps</p> +<p>First install Goaccess on Arch Linux</p> +<code>sudo pacman -S goaccess</code> +<p>View the remote stats (adjust the path for your actual log location)</p> +<code>ssh USER@SERVER 'zcat -f /var/log/nginx/access.log*' | goaccess -a --log-format=COMBINED</code> +<p>I've added an alias in my .bashrc :</p> +<code>alias stats="ssh USER@SERVER 'zcat -f /var/log/nginx/access.log*' | goaccess -a --log-format=COMBINED"</code> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/VPS-management.html b/articles/2025/VPS-management.html new file mode 100755 index 0000000..77d77c6 --- /dev/null +++ b/articles/2025/VPS-management.html @@ -0,0 +1,33 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>VPS management</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>VPS management</h2> +<h3>2025-04-18</h3> +<h4>Reducing things</h4> +<p>I have 2 vps's that were underused, so I've put everything on to a single vps, which has the benefit of halving costs.</p> +<p>My days of writing and hosting web sites for others are over, I just have to cater for myself now.</p> +<p>Which is quite nice really...</p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/What-to-do-with-an-unused-Kindle.html b/articles/2025/What-to-do-with-an-unused-Kindle.html new file mode 100755 index 0000000..de8bf3c --- /dev/null +++ b/articles/2025/What-to-do-with-an-unused-Kindle.html @@ -0,0 +1,33 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>What to do with an unused Kindle</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>What do you do with an unused Kindle</h2> +<h3>2025-04-17</h3> +<h4>Early in the morning...</h4> +<p>My Kindle paperwhite is sleeping in a drawer since I bought a Kobo, but there's something I can do with it.</p> +<p>Run Linux, use Koreader, play games ... +<p><a href="https://youtu.be/Qtk7ERwlIAk">It's time to jailbreak your Kindle</a></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Why-does-everything-feel-so-boring-now.html b/articles/2025/Why-does-everything-feel-so-boring-now.html new file mode 100755 index 0000000..e0ef08b --- /dev/null +++ b/articles/2025/Why-does-everything-feel-so-boring-now.html @@ -0,0 +1,48 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>Why does everything feel so boring now?</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + + <main> + <article> + <h2>Why does everything feel so boring now?</h2> + <h3>2025-06-07</h3> + <h4>Food for thought on blandness</h4> + <p>What happened to colour? Where did it go?</p> + <p>I wonder if we have reached peak blandness.</p> + <div class="wrapper"> + <iframe + src="https://www.youtube.com/embed/TQSwDenTM88" + title="Why does everything feel so boring now?" + frameborder="0" + allow=" + accelerometer; + autoplay; + clipboard-write; + encrypted-media; + gyroscope; + picture-in-picture; + web-share; + " + referrerpolicy="strict-origin-when-cross-origin" + allowfullscreen + ></iframe> + </div> + </article> + </main> + <footer> + <p> </p> + + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2025/Wifi-region.html b/articles/2025/Wifi-region.html new file mode 100755 index 0000000..2a1957b --- /dev/null +++ b/articles/2025/Wifi-region.html @@ -0,0 +1,33 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Wifi region</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Wifi region</h2> +<h3>2025-03-26</h3> +<h4>If like me you reside in France, you may have wifi problems with the default frequency settings.</h4> +<p>Verify the value:</p> +<code>iw reg get</code> +<p>If you don't see FR then it's not correctly set.</p> +<code>sudo iw reg set FR</code> +<p>will correct that. To make it persistant, edit /etc/default/crda and add</p> +<code>REGDOMAIN=FR</code> +<br> +<a href="https://sebsauvage.net/wiki/doku.php?id=linux-vrac">Source</a></p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/Window-manager-thoughts.html b/articles/2025/Window-manager-thoughts.html new file mode 100755 index 0000000..e8dfed3 --- /dev/null +++ b/articles/2025/Window-manager-thoughts.html @@ -0,0 +1,47 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Window manager thoughts</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Window manager thoughts</h2> +<h3>2025-04-15</h3> +<h4>Messing with i3, Openbox, and Xfce4</h4> +<p>Listening to Youtube "influencers" I learned that I was a dummy because I don't use tiling window managers.</p> +<p>I tried i3 in Manjaro before but just didn't connect with it. Perhaps it was me? +Trying again I spent the past couple of days messing with the configuration files of i3, i3blocks, polybar, etc. +again, just to see if maybe I missed out on something.</p> +<p>Turns out I didn't.</p> +<p>My Thinkpad X220 is my daily drive, it has a 13" screen, 1366x768, and a French keyboard.</p> +<p>Splitting the screen is a daft idea on such a small laptop, it doesn't add any productivity improvement +even when you cast aside the problems remembering was it Super+~ or was it Super+Shift+F2 ?, +and the resulting digital gymnastics (although possibly good for my carpal tunnels, who knows?)</p> +<p>It's understandable when you have a nice large desktop screen and a decently sized keyboard, but here no way.</p> +<p>Anyway, a stacking windowmanager, Xfce4, appears to be the best bang for my buck teeth on this laptop.</p> +<p>I have set the keystroke Super+right & left to flip through windows that I mostly have maximised +(I'll eventually use devilspie to make this automatic), +that are all simply stacked on top of each other in the same workspace. +I use tabs in Ghostty to have for example Neomutt, btop, and a command line. In Librefox I have tabs too, +and I'll probably have Libreoffice Calc or Writer open, Gimp, galculator too.</p> +<p>Simple and effective, while keeping a bottom panel and application menu available.</p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Workshop-Air-Cleaner.html b/articles/2025/Workshop-Air-Cleaner.html new file mode 100755 index 0000000..307a2c5 --- /dev/null +++ b/articles/2025/Workshop-Air-Cleaner.html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Workshop Air Cleaner</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>Workshop Air Cleaner</h2> +<h3>2025-03-19</h3> +<h4>Keeping the garage clean-ish</h4> +<img src="/images/recordpower_AC400.jpg"> +<p>I've cancelled the plan to build a workshop. It's expensive, and increases my local taxes.</p> +<p>So I'm adapting the shared garage. The first step is an air cleaner, I settled for the Record Power AC400</p> +<p>I chained it to the rafters, and first results are good.</p> +</article> +</main> +<footer> +<p> </p><p>§</p></footer> +</body></html> diff --git a/articles/2025/X220-internal-Bluetooth-controller.html b/articles/2025/X220-internal-Bluetooth-controller.html new file mode 100755 index 0000000..dd8f337 --- /dev/null +++ b/articles/2025/X220-internal-Bluetooth-controller.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>X220 internal Bluetooth controller</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>X220 internal Bluetooth controller</h2> +<h3>2025-05-27</h3> +<h4>Deactivating the internal Bluetooth controller</h4> +<p>The Thinkpad X220 has an internal Bluetooth controller, but +<a href="/articles/2025/AC7260-wifi-card-on-Thinkpad-X220.html">I installed the AC7260 wifi/bluetooth card</a>.</p> +<p>Unfortunately the default card remains the internal one, so autoconnect of my headset doesn't work.</p> +<p>In order to block the internal device, I made a UDEV rule in /etc/udev/rules.d/91-bluetooth-hci.rules</p> +<p>After obtaining the id with lsusb:</p> +<pre>SUBSYSTEM=="usb", ATTRS{idVendor}=="8087", ATTRS{idProduct}=="07dc", ATTR{authorized}="0"</pre> +<p>Reboot, and the AC7260 Bluetooth card is now the default.</p> + + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/Xlibre-on-my-X220.html b/articles/2025/Xlibre-on-my-X220.html new file mode 100755 index 0000000..c3bc3d8 --- /dev/null +++ b/articles/2025/Xlibre-on-my-X220.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Xlibre on my X220</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + <main> +<article> +<h2>Xlibre on my X220</h2> +<h3>2025-08-07</h3> +<h4>Xlibre > Xorg</h4> +<p>Not much to post about recently as I've been busy building myself a new shed to store my recuperated hardwood collection.</p> +<p>However, I just installed Xlibre, a fork of Xorg. It's now available in the Artix Galaxy repo, and it went without a hitch.</p> +<code>pacman -S xlibre</code> +<p>Allow xlibre to replace all the xorg packages, then install the video driver package you need.</p> +<code>pacman -S xlibre-xf86-video-intel</code> +<p>For more info on Xlibre, check out the site <a href="https://x11libre.net/#about">https://x11libre.net</a></p> + </article> +</main> +<footer> + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/dwm-windowmanager-1.html b/articles/2025/dwm-windowmanager-1.html new file mode 100755 index 0000000..5b19d06 --- /dev/null +++ b/articles/2025/dwm-windowmanager-1.html @@ -0,0 +1,46 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>test</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>DWM Window manager [1]</h2> +<h3>2025-04-21</h3> +<h4>Suckless window manager</h4> +<p><a href="https://www.suckless.org" target="_blank">Suckless web site</a></p> +<p>It struck me that using DWM with Monocle layout would be just what I've been looking for on my X220</p> +<p>I cloned the st, dmenu, and dwm from suckless.org and followed the instructions to compile them.</p> +<p>Right now I have Xfce4 installed, so to try out DWM I've added an entry to select it in lightdm</p> +<p><b>sudo micro /usr/share/xsessions/dwm.desktop</b></p> +<code>[Desktop Entry] +Encoding=UTF-8 +Name=dwm +Comment=Dynamic window manager +Exec=dwm +Icon=dwm +Type=XSession +</code> +<p>In dwm's config.h I set the monocle layout as default and recompiled dwm.</p> +<p>At first glance this looks much better than the i3 window manager, small, neat and fast.</p> +<p>More to come...</p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/dwm-windowmanager-2.html b/articles/2025/dwm-windowmanager-2.html new file mode 100755 index 0000000..27e5a63 --- /dev/null +++ b/articles/2025/dwm-windowmanager-2.html @@ -0,0 +1,64 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>DWM window manager [2]</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>DWM window manager [2]</h2> +<h3>2025-04-23</h3> +<h4>the simpler the better</h4> +<p>Mucking around with DWM proves the old adge that less is more. +So far I haven't needed to add any of the myriad of patches available for DWM, it works as it is for me.</p> +<p>I've tried dwm-blocks but right now I'm using dwm-bar as I like the battery icon to update along with the charge.</p> +<p>I have edited config.h though and changed the keybindings to work better with a French keyboard and launch a few apps.</p> +<img src="/images/blog/dwm-btop.png" alt="dwm running btop"> +<p>Editing config.h is an ongoing job, but you can <a href=/txt/dwm-config.txt>see a snapshot here</a></p> +<p>ST is the default terminal emulator for DWM but I rather like Ghostty even if it consumes a bit more +memory. Adding <code>window-decoration = none</code> to <b>.config/ghostty/config</b> makes it look better in DWM.</p> +<p>I also added <code>gtk-single-instance = true</code> to make ghostty's startup faster.</p> +<p>Being able to swap between display layouts on the fly is cool. I mostly use monocle layout, +but flipping to tiling or floating comes in handy.</p> +<p>I removed the lightdm display manager and installed Slim. Here is my <b>.xinitrc</b></p> +<pre> +#!/bin/sh + +# Source global X session scripts. +if [ -d /etc/X11/xinit/xinitrc.d ]; then + for f in /etc/X11/xinit/xinitrc.d/*; do + [ -x "$f" ] && . "$f" + done + unset f +fi + +bluetooth off +clipmenud & +xscreensaver --no-splash & +ghostty & +feh --bg-scale --randomize ~/Pictures/wallpaper/* & +~/.local/src/dwm-bar/dwm_bar.sh & +# if dwm exits 0, restart -- this allows hot reloading of config.h +while type dwm >/dev/null ; do dwm && continue || break ; done +</pre> +<p>And here is my DWM configuration file (it probably isn't the latest version) <br> +<a href="/txt/dwm-config.txt">config.h</a></p> +</article> +</main> +<footer> + +<p> </p> + +<p>§</p> + </footer> +</body> +</html> diff --git a/articles/2025/dwmblocks-with-emoji-icons.html b/articles/2025/dwmblocks-with-emoji-icons.html new file mode 100755 index 0000000..5817367 --- /dev/null +++ b/articles/2025/dwmblocks-with-emoji-icons.html @@ -0,0 +1,50 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>dwmblocks with emoji icons</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + + </header> + +<main> +<article> +<h2>dwmblocks with emoji icons</h2> +<h3>2025-06-04</h3> +<h4>fancy blocks for dwm</h4> +<p>Just a pic of my simple dwmblocks status bar</p> +<p><img src="/images/blog/dwmblocks-emoji.png"></p> +<p>The calendar app is Orage, opened and closed by clicking on the date which launches <a href="https://git.wittamore.fr/scripts/tree/pw-date">a dwmblocks script</a></p> +<h4>dwm/config.def.h</h4> +<pre style="font-size:10px;white-space: pre;"> +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask iscentered isfloating isterminal noswallow monitor */ + { "Gimp", NULL, NULL, 0, 0, 1, 0, 0, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, 0, 0, -1, -1 }, + { "Ghostty", NULL, NULL, 0, 0, 0, 1, 0, -1 }, + { "Orage", NULL, NULL, 0, 0, 1, 0, 1, -1 }, + { "Galculator", NULL, NULL, 0, 1, 1, 0, 1, -1 }, + { NULL, NULL, "Event Tester", 0, 0, 0, 0, 1, -1 }, /* xev */ +}; +</pre> + + </article> +</main> +<footer> + + <p> </p> + + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2026/De-Gaulle---Two-films.html b/articles/2026/De-Gaulle---Two-films.html new file mode 100644 index 0000000..5be3c14 --- /dev/null +++ b/articles/2026/De-Gaulle---Two-films.html @@ -0,0 +1,63 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>De Gaulle - Two films</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>De Gaulle - Two films</h2> + <h3>2026-06-04</h3> + <h4>De Gaulle is more popular than ever</h4> + <p> + People who choose a country to be their home are often patriotic as + it's their choice, not mere circumstance. + </p> + <p>Personally I'm patriotic but certainly not nationalist.</p> + <blockquote> + <i + >“The difference between patriotism and nationalism is that the + patriot is proud of his country for what it does, and the + nationalist is proud of his country no matter what it does; the + first attitude creates a feeling of responsibility, but the second a + feeling of blind arrogance that leads to war.”</i + > + ― + <a href="https://en.wikipedia.org/wiki/Sydney_J._Harris" + >Sydney J. Harris</a + > + </blockquote> + <p></p> + <div class="wrapper"> + <iframe + src="https://www.youtube.com/embed/RTNzSKr3Hsw" + title="De Gaulle (2026) Official Trailer" + frameborder="0" + allow=" + accelerometer; + autoplay; + clipboard-write; + encrypted-media; + gyroscope; + picture-in-picture; + web-share; + " + referrerpolicy="strict-origin-when-cross-origin" + allowfullscreen + ></iframe> + </div> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Filtering-the-web.html b/articles/2026/Filtering-the-web.html new file mode 100644 index 0000000..0a94d16 --- /dev/null +++ b/articles/2026/Filtering-the-web.html @@ -0,0 +1,62 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Filtering the web</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Filtering the web</h2> + <h3>2026-06-20</h3> + <h4>A browser that says no</h4> + <p> + To me the problem with the smolweb is that there's isn't a browser + that applies these rules. Dillo comes close, even though it needs a + makeover. + </p> + <p> + Dillo also has an rdrview plugin, but you have to manually write + rdrview: in front of the url in the url bar of every page to see the + results. + </p> + <p> + A better solution would be a smolweb plugin that filters the entire + html page rejecting anything that doesn't + <a href="https://smolweb.org/validator/">validate</a> or pass to + rdrview . Degrees of acceptance could be set in settings. + </p> + <p> + A dedicated browser wouldn't require searching for smolweb / smallweb + / indieweb blogs, sites, or articles. (There seems to be no such thing + as a smolweb search engine, although points for wiby, marginalia, kagi + and mojeek). + </p> + <p> + Just point the browser to any web site and if it borks, display the + "errors" or ignore it with a + <a + href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/418" + > + 418 I'm a teapot error</a + >: after all it's the web site author's responsibility to provide + clean, readable web sites. + </p> + <p> + Imagine a world where this plugin would be as ubiquitous as + ublock-origin, it could even change web site development. + </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Freedos-thoughts.html b/articles/2026/Freedos-thoughts.html new file mode 100644 index 0000000..d16485e --- /dev/null +++ b/articles/2026/Freedos-thoughts.html @@ -0,0 +1,43 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Freedos thoughts</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Freedos thoughts</h2> + <h3>2026-06-11</h3> + <h4>Maybe I have the best system already</h4> + <p> + I've been looking at Freedos for the retrocomputing nostalgia value + and the text-only goodness. + </p> + <p> + I wound up using Qemu on my X270 as Freedos on bare metal means my 14 + year old Thinkpad X220 is way too modern. + </p> + <p> + Yes there's some crazy good software, and it's fun. But using + fullscreen in Qemu means a fuzzy display, and I came to measure how + the terminal on Linux is truly light years ahead. + </p> + <p> + In fact it pushes home that my Artix / DWM / Kitty setup is the + veritable retrocomputing bees knees + </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/From-minimalist-to-smolweb.html b/articles/2026/From-minimalist-to-smolweb.html new file mode 100644 index 0000000..54de0bd --- /dev/null +++ b/articles/2026/From-minimalist-to-smolweb.html @@ -0,0 +1,43 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>From minimalist to smolweb</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + + <main> + <article> + <h2>From minimalist to smolweb</h2> + <h3>2026-05-06</h3> + <h4>When smarter people get things done</h4> + <p> + In 2023 + <a href="/articles/2023/minimalist-blog.html">I wrote an article</a> + about what I was feeling at the time on the sorry state of the web. + </p> + <p> + I'm happy to say that the people at + <a href="https://smolweb.org">smolweb.org</a> have made a far better + statement than mine. + </p> + <p> + As this blog is now a few years old, I'll be working on making it pass + the + <a href="https://smolweb.org/validator/">smolweb validation test</a>. + </p> + </article> + </main> + <footer> + <p> </p> + + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Funeral-blues.html b/articles/2026/Funeral-blues.html new file mode 100644 index 0000000..31f4f9b --- /dev/null +++ b/articles/2026/Funeral-blues.html @@ -0,0 +1,57 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Funeral blues</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Funeral blues</h2> + <h3>2026-05-07</h3> + <h4>A poem by W.H.Auden (1907-1973)</h4> + <p>This poem was read in the film "Four marriages and a funeral"</p> + + <p>I just stumbled upon it and wanted to write it down.</p> + + <code> +Funeral Blues + +Stop all the clocks, cut off the telephone. +Prevent the dog from barking with a juicy bone, + +Silence the pianos and with muffled drum +Bring out the coffin, let the mourners come. + +Let aeroplanes circle moaning overhead +Scribbling in the sky the message He is Dead, + +Put crêpe bows round the white necks of the public doves, +Let the traffic policemen wear black cotton gloves. + +He was my North, my South, my East and West, +My working week and my Sunday rest + +My noon, my midnight, my talk, my song; +I thought that love would last forever, I was wrong. + +The stars are not wanted now; put out every one, +Pack up the moon and dismantle the sun. + +Pour away the ocean and sweep up the wood; +For nothing now can ever come to any good. + </code> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Ghostbsd-installation.html b/articles/2026/Ghostbsd-installation.html new file mode 100644 index 0000000..cf3314e --- /dev/null +++ b/articles/2026/Ghostbsd-installation.html @@ -0,0 +1,92 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Ghostbsd installation</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Ghostbsd installation</h2> + <h3>2026-06-14</h3> + <h4>messing around on a sunday</h4> + <p> + So now we have Microsoft delivering Linux, and the Arch based AUR + getting wrecked by people who should be locked into a public pillory, + and I have some free time. Well in fact a lot of free time now that + I'm retired. + </p> + <p> + So. I downloaded the latest #Ghostbsd Mate and cut a usb key, then + plugged it into my Thinkpad X220. + </p> + <p> + The installation was simple and uneventful, and it booted without + problem. Everything worked out of the box. Mate seems pretty and + unobfuscated, just like me. + </p> + <p>So far I've done just two things:</p> + <p> + (1) to get some thinkpad function keys to work I've added in + /boot/loader.conf + </p> + <pre> +acpi_ibm_load="YES" +</pre + > + <p> + Printscreen works, brightness and volume controls work, vol mute + works, lid led works, but the MICMUTE button doesn't work.<br /> + For the X220 MICMUTE key, which is F20 or 0xBE (190), unfortunately + Ghostbsd's acpi_ibm doesn't pass on the event, so any script for it in + /etc/devd won't be fired. Checking the source code, acpi_ibm only accepts + a subset of events accepted by Linux's thinkpad_acpi, which appears to + cover them all. It's unfortunate as I'm a bit pedantic about keys but I + guess I'll have to live with it as the code is 20 years old and I + doubt it will change anytime soon. + </p> + + <p>FYI, acpi_ibm accepts only these keyboard events:</p> + <PRE> + LCD_BACKLIGHT + SUSPEND_TO_RAM + BLUETOOTH + SCREEN_EXPAND + SUSPEND_TO_DISK + BRIGHTNESS_UP + BRIGHTNESS_DOWN + THINKLIGHT + ZOOM + VOLUME_UP + VOLUME_DOWN + MUTE + ACCESS_IBM_BUTTON + </pre> + <p>(2) to enable the automounting of usb storage media:</p> + <pre> +sudo pkg install automount +</pre + > + <p> + I've replaced the default Firefox with Librewolf, and I'm not too sure + about zsh instead of bash, and why not doas instead of sudo? + </p> + <p> + Ghostbsd is a very nice BSD distribution that's been around since + 2010. + </p> + <p><a href="https://www.ghostbsd.org/">https://www.ghostbsd.org</a></p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Hebdogiciel.html b/articles/2026/Hebdogiciel.html new file mode 100644 index 0000000..fdc10e4 --- /dev/null +++ b/articles/2026/Hebdogiciel.html @@ -0,0 +1,51 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Hebdogiciel</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Hebdogiciel</h2> + <h3>2026-06-07</h3> + <h4>Flashback to the 80's</h4> + <p> + After my ZX81 I purchased an Amstrad CPC464, and oh joy I discovered + Hebdogiciel. Before the internet this magazine printed the code for + games and programs that you could painfully copy out. Today I + discovered that there's an online archive: + </p> + + <p> + <a + href="https://www.abandonware-magazines.org/affiche_mag.php?mag=7&page=1" + >abandonware-magazines</a + > + </p> + + <p>A translation of the French abandonware-magazine article:</p> + <p> + <b>1983–1987:</b> Imagine a weekly magazine that, in addition to + providing us with a weekly program to type out and delivering + uncompromising hardware and software reviews (and that’s saying + something), also featured TV listings, movie, music, and comic book + releases, assembly language tutorials, a comic strip, a discount club, + walkthroughs... A tone that blew you away from the first page to the + last, Carali’s drawings filling in the gaps, lawsuits galore… Simply + extraordinary, unmatched. + </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Installation-of-Snac2-on-Artix-Linux.html b/articles/2026/Installation-of-Snac2-on-Artix-Linux.html new file mode 100644 index 0000000..2b752ea --- /dev/null +++ b/articles/2026/Installation-of-Snac2-on-Artix-Linux.html @@ -0,0 +1,144 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link rel="stylesheet" href="/style.css"> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>Installation of Snac2 on Artix Linux</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> +<h2>Installation of Snac2 on Artix Linux</h2> +<h3>2026-05-20</h3> +<h4>Installation of snac2 on Artix Linux +Nginx +Dinit</h4> +<pre> +useradd -m -s /bin/bash snac +passwd snac +usermod -aG wheel snac +su - snac +mkdir src +cd src +git clone https://codeberg.org/grunfink/snac2.git +cd snac2 +make +sudo make install +snac init ~/data +</pre> +<h3>snac init will ask these questions:</h2> +<pre> +Host name: your.server.domain +Path prefix: leave empty +Network address: 127.0.0.1 +Port: 8001 +</pre> +<h3>add user</h3> +<pre> +snac adduser ~/data +</pre> +<h3> In /home/snac/data/server.json edit these settings:</h3> +<pre> +admin_email +admin_account +title +short_description + +and add this line: + +"proxy_media: true +</pre> +<h3> As root, create the dinit script: /etc/dinit.d/snac2</h3> +<pre> +type = process +run-as = snac +command = /usr/local/bin/snac httpd /home/snac/data +depends-on = NetworkManager +restart = true +smooth-recovery = yes +log-type = file +logfile = /var/log/dinit/snac2.log +</pre> +<h3>enable snac2</h3> +<pre> +dinitcl enable snac2 +</pre> +<h3>NGINX Media caching</h3> +<p> +see <a href="https://it-notes.dragas.net/2025/01/29/improving-snac-performance-with-nginx-proxy-cache/">it-notes.fragas.net</a> +</p> +<h3>Nginx configuration +<p> +see <a href="https://comam.es/snac-doc/snac.8.html">comam.es</a>.<br/> +Use $host not $http_host (gixy) +</p> +<h3>in /etc/nginx/nginx.conf http section add:</h3> +<pre> +proxy_cache_path /var/cache/nginx/snac_cache levels=1:2 keys_zone=snac:10m max_size=1g inactive=1440m use_temp_path=off; +</pre> +<h3>make cache folder</h3> +<pre> +mkdir -p /var/cache/nginx/snac_cache +</pre> +<h3>/etc/nginx/sites-available/example.com</h3> +<pre> +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name example.com; + + ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/example.com/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + # openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + +location / { + proxy_pass http://127.0.0.1:8001; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $remote_addr; +} + +location ~ ^/.+/s/ { + proxy_cache snac; + proxy_pass http://127.0.0.1:8001; + proxy_set_header Host $host; + proxy_cache_valid 200 1d; + proxy_cache_valid 404 1h; + proxy_ignore_headers "Cache-Control" "Expires"; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; + proxy_cache_lock on; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Proxy-Cache $upstream_cache_status; +} +} + +server { + listen 80; + listen [::]:80; + server_name example.com; + return 301 https://$host$request_uri; +} +</pre> + +<h3>add pleroma relay</h3> +<p>see <a href="https://relaylist.com/">relaylist.com</a></p> +<pre> +su - snac +snac adduser ~/data relay +snac follow ~/data relay https://relay.toot.io/actor +</pre> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Ladybird-turns-into-a-squashed-bug.html b/articles/2026/Ladybird-turns-into-a-squashed-bug.html new file mode 100644 index 0000000..c6039f7 --- /dev/null +++ b/articles/2026/Ladybird-turns-into-a-squashed-bug.html @@ -0,0 +1,45 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Ladybird turns into a squashed bug</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Ladybird turns into a squashed bug</h2> + <h3>2026-06-06</h3> + <h4>Sponsored software hype</h4> + <p> + Last year + <a href="https://wittamore.fr/articles/2025/Ladybird-web-browser.html" + >I had high hopes for Ladybird</a + >, a web browser with a brand new engine written, from scratch and + open source. + </p> + <p> + Unfortunately + <a href="https://ladybird.org/posts/changing-how-we-develop-ladybird/" + >the code has now been locked</a + >, and the code was moved from Swift to Rust because ... AI code. + </p> + <p> + --> + <a href="https://exquisite.social/@thomholwerda/116697078838020135" + >The fash browser developed by "AI" hypeboys</a + > + </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Living-on-Snac's-bleeding-edge.html b/articles/2026/Living-on-Snac's-bleeding-edge.html new file mode 100644 index 0000000..a17ffc6 --- /dev/null +++ b/articles/2026/Living-on-Snac's-bleeding-edge.html @@ -0,0 +1,60 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Living on Snac's bleeding edge</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Living on Snac's bleeding edge</h2> + <h3>2026-05-25</h3> + <h4>Autoupdate script</h4> + <p> + I like living on the bleeding edge of + <a href="https://codeberg.org/grunfink/snac2">snac2</a>, so I added + this (banal) script to /etc/cron.daily. + </p> + <pre> +#!/usr/bin/env bash +# snaceto +# Social Networks Are Crap Except This One + +cd /home/snac/src/snac2 || exit + +sudo -u snac git remote update +UPDATED="$(git status -uno | grep -c 'up to date')" + +if [ "$UPDATED" -ne "1" ] ; then + echo "Updating..." + systemctl stop snac.service + # backup data before update + TODAY=$(date "+%Y-%m-%d-%H-%m") + BACKUPDIR="/opt/backups" + mkdir -p "$BACKUPDIR" + FILENAME="$BACKUPDIR/pre-update-snac-$TODAY.tar.gz" + tar -zcf "$FILENAME" /home/snac/data + # pull latest + sudo -u snac git pull + sudo -u snac make + make install + sudo -u snac snac upgrade /home/snac/data + systemctl start snac.service + VERSION="$(snac | head -n2)" + toot post "My bleeding edge #snac2 server has been updated<br>$VERSION" +fi +</pre> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/More-on-Dillo.html b/articles/2026/More-on-Dillo.html new file mode 100644 index 0000000..862574e --- /dev/null +++ b/articles/2026/More-on-Dillo.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content= + "text/html; charset=utf-8"> + <meta name="viewport" content= + "width=device-width, initial-scale=1.0"> + <link rel="stylesheet" href="/style.css"> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>More on Dillo</title> +</head> +<body> + <header> + <h1>Bloggings</h1><a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>More on Dillo</h2> + <h3>2026-06-21</h3> + <h4>update on yesterday's post</h4> + <p>The <a href="https://git.dillo-browser.org/dillo">git + version of dillo</a> has a new option: page_action, which + enables running rdrview on the current page if the + setting:</p> + <pre><code>page_action="Reader mode:dilloc dump | rdrview -H | dilloc load" +</code></pre> + <p>is set in dillorc.</p> + <p>When you right click on a page the menu will now include + the option "Reader Mode".</p> + </article> + </main> + <footer> + <p>§</p> + </footer> +</body> +</html> diff --git a/articles/2026/My-own-private-search-engine.html b/articles/2026/My-own-private-search-engine.html new file mode 100644 index 0000000..e2c924e --- /dev/null +++ b/articles/2026/My-own-private-search-engine.html @@ -0,0 +1,54 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>My own private search engine</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>My own private search engine</h2> + <h3>2026-06-02</h3> + <h4>Messing around with a bad back in stormy weather</h4> + <p> + I made a bad movement in the shower this morning and made my back + scream again, so no woodworking today. One of the benefits of + retirement is that I get to choose the days activities. + </p> + <p> + I had installed + <a href="https://github.com/searxng/searxng">SearxNG</a> previously + more than two years ago, but as I'm constantly fiddling it wound up in + the bin. + </p> + <p> + Convalescing in the armchair I saw that my current vps is mostly idle + running just snac2, my blog, xmpp, gophernicus and other bits, so I + reinstalled SearxNG. + </p> + <p> + It's fast, responsive, and private. I have it behind an Nginx reverse + proxy, and only accepting my own IP address for now. + </p> + <p> + Another thing I did was to play around with the + <a href="https://getfresh.dev/">Fresh editor</a>. Like Micro, it can + be configured to use prettier, the code formatter. Ok, it's written in + Rust, but it is a nice editor with a retro computing vibe. It won't + replace Micro as my daily editor, but if I ever need a TUI IDE it + could be a good choice. + </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Oops!-I-did-it-again.html b/articles/2026/Oops!-I-did-it-again.html new file mode 100644 index 0000000..bd3bf4f --- /dev/null +++ b/articles/2026/Oops!-I-did-it-again.html @@ -0,0 +1,46 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Oops! I did it again</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Oops! I did it again</h2> + <h3>2026-05-31</h3> + <h4>More blog improvements</h4> + <p> + This blog now + <a href="https://git.wittamore.fr/bloggings/tree/" + >sits in my git repository</a + >, so I made a few changes. + </p> + <p> + One is the + <a href="https://git.wittamore.fr/blog-scripts/tree/blogsend" + >blogsend</a + > + bash script which now does a git commit on my local machine, and a git + pull on the remote web server. The other is the git repos itself which + now has the web site in one repos, and the bash scripts in another. + </p> + <p> + All this is probably of little interest to anyone other than myself, + but if you want to run a smol blog you might find it useful, after all + that's why I'm posting it. + </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/This-blog-wasn't-so-bad-after-all.html b/articles/2026/This-blog-wasn't-so-bad-after-all.html new file mode 100644 index 0000000..f0392cd --- /dev/null +++ b/articles/2026/This-blog-wasn't-so-bad-after-all.html @@ -0,0 +1,46 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/style.css" /> + <title>This blog wasn't so bad after all</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + + <main> + <article> + <h2>This blog wasn't so bad after all</h2> + <h3>2026-05-05</h3> + <h4>Smolweb, Fediverse and blogs</h4> + <p> + For several months I've been using Gopher exclusively, I find it's + minimalist raw text files appealing. + </p> + <p> + It does have several drawbacks however. Recently I've been self + hosting my own fediverse instance at + <a href="https://social.wittamore.fr">social.wittamore.fr</a>. + </p> + <p> + Looking around for a fediverse blog, I realised that this html blog + wasn't so bad after all. + </p> + <p> + It's work in progress, as I would like to have it + <a href="https://smolweb.org/">smolweb validated</a>, and needs a + clean up. + </p> + </article> + </main> + <footer> + <p> </p> + + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Updated-blogthis-script.html b/articles/2026/Updated-blogthis-script.html new file mode 100644 index 0000000..6bb2ad7 --- /dev/null +++ b/articles/2026/Updated-blogthis-script.html @@ -0,0 +1,38 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Updated blogthis script</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Updated blogthis script</h2> + <h3>2026-06-16</h3> + <h4>now with markdown</h4> + <p> + I've updated my blogthis script so I can write articles in markdown. + </p> + <p> + It saves me having to write html in the articles and avoids errors. + </p> + <p> + I've uploaded it to + <a href="https://git.wittamore.fr/blog-scripts/tree/" + >my git server</a + > + </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Updated-poem.html b/articles/2026/Updated-poem.html new file mode 100644 index 0000000..ad2a02d --- /dev/null +++ b/articles/2026/Updated-poem.html @@ -0,0 +1,45 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Updated poem</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Updated poem</h2> + <h3>2026-06-01</h3> + <h4>Existance is the prize itself</h4> + <p> + Sadly a childhood friend of one of my nieces recently died at 36 of a + brain tumour. I gave this poem a re-write. + </p> + <pre> +Life is but a long farewell +While clawing at eternity's shell +No rhyme nor reason, no justice in sight +A frantic push against entropy's might +This fleeting hare +Brings grave dispair +But pointless is a daily dread +Of closing circuit in our bed +Here then gone, a spark in the night? +Our passing through is never trite +Best keep your malaise on the shelf +Existance is the prize itself +</pre + > + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/Web-sites-that-work-well-in-Dillo.html b/articles/2026/Web-sites-that-work-well-in-Dillo.html new file mode 100644 index 0000000..42caa89 --- /dev/null +++ b/articles/2026/Web-sites-that-work-well-in-Dillo.html @@ -0,0 +1,60 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Web sites that work well in Dillo</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>Web sites that work well in Dillo</h2> + <h3>2026-06-19</h3> + <h4>and other minimal browsers</h4> + <p> + I'm always looking for sane web sites. By sane I mean websites that + are not bloated with useless eye candy, nor have any ketamin fired + javascript. Sites that just want to display data in the form of easily + readable text with or without some images. + </p> + <p> + There is an increasing number of web sites that do this, as it's a + normal backlash to the slop infested, obfuscated tar pit that the + internet has sadly become. I haven't found a single compendium of + sites, but I'll list some here. + </p> + <p> + One of the things I find realy interesting are web proxies that render + sites into a saner version, like + <a href="https://www.textise.net/">Textise</a> or + <a href="https://txt.dc09.xyz/">Textdot</a>. I fact I'm wondering if I + could roll my own simple version using php and rdrview. + </p> + <p> + This list incomplete list will be updated as I find them, or maybe you + dear reader might have suggestions? + </p> + <p> + <a href="https://dir.dillo-browser.org/" + >Directory of useful sites that work reasonably well on Dillo</a + > + </p> + <p><a href="https://wiby.me/">Wiby search engine</a></p> + <p> + <a href="https://ronaldsvilcins.com/2021/09/15/text-only-websites/" + >Text only web sites</a + > + </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/displaying-MATE-on-a-1366x768-screen.html b/articles/2026/displaying-MATE-on-a-1366x768-screen.html new file mode 100644 index 0000000..da49491 --- /dev/null +++ b/articles/2026/displaying-MATE-on-a-1366x768-screen.html @@ -0,0 +1,73 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>displaying MATE on a 1366x768 screen</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>displaying MATE on a 1366x768 screen</h2> + <h3>2026-06-15</h3> + <h4>xrandr to the rescue</h4> + <p> + Unfortunately several Ghostbsd MATE configuration windows are taller + than my Thinkpad X220's 768 pixels. I made both the top and bottom + panels autohide, but a better solution is to use xrandr: + </p> + <pre>sudo pkg install xrandr</pre> + <p> + type xrandr to get the name of your display, in my case LVDS-1 on my + Thinkpad X220. + </p> + <p>To change 16:9 screen resolution, you can type:</p> + <pre> +xrandr --output LVDS-1 --panning 1600x900 --scale 1.171x1.172</pre + > + <p>or</p> + <pre> +xrandr --output LVDS-1 --panning 1920x1080 --scale 1.406x1.406</pre + > + <p>To get back to the original resolution type:</p> + <pre>xrandr --output LVDS-1 --panning 1366x768 --scale 1x1</pre> + <p> + Here's a simple bash script that will rotate the screen resolution on + each call. I've added a MATE keyboard shortcut (CTRL+RIGHT) to run + this script. It is intended for my 1366x768 screen, so it will need + adapting for other screens. + </p> + <pre> +#!/usr/bin/env bash + +RES="$(xdpyinfo | grep dimensions | sed -r 's/^[^0-9]*([0-9]+x[0-9]+).*$/\1/')" +# xdpy results can vary by 1 or two pixels +# so take the first two characters +RES="${RES:0:2}" + +case "$RES" in + "13") + xrandr --output LVDS-1 --panning 1600x900 --scale 1.171x1.172 + ;; + "16") + xrandr --output LVDS-1 --panning 1920x1080 --scale 1.406x1.406 + ;; + *) + xrandr --output LVDS-1 --panning 1366x768 --scale 1x1 + ;; +esac + </pre> + <p>I doubt you can do that in Wayland.</p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/semware-tse-editor.html b/articles/2026/semware-tse-editor.html new file mode 100644 index 0000000..441df10 --- /dev/null +++ b/articles/2026/semware-tse-editor.html @@ -0,0 +1,44 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>semware-tse-editor</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>semware-tse-editor</h2> + <h3>2026-06-13</h3> + <h4>A very interesting editor for DOS, Linux and Windows</h4> + <p> + <img src="/images/blog/semware.png" alt="semware editor screenshot" /> + </p> + <p> + I stumbled upon the #Semware editor (TSE) for #Linux, Windows and #Dos + and thought I'd share it here. Originally named Qedit in 1985, the + latest version is dated May 2026. I don't know about Windows (as I + haven't used it in years), but the Linux version is a pleasant CLI app + with many features, including it's own application language. The more + I look at it, the more I realise that it is a profound piece of + software. + </p> + <p> + <a href="https://www.semware.com/">www.semware.com</a><br /> + <a href="https://www.youtube.com/@knudvaneeden" + >Knud Vaneeded YT videos</a + > + </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/smolweb-validation-in-Dillo.html b/articles/2026/smolweb-validation-in-Dillo.html new file mode 100644 index 0000000..38e543d --- /dev/null +++ b/articles/2026/smolweb-validation-in-Dillo.html @@ -0,0 +1,57 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>smolweb validation in Dillo</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>smolweb validation in Dillo</h2> + <h3>2026-06-22</h3> + <h4>a simple page_action script</h4> + <p> + I added smolweb-validator as a page action in Dillo. It's a bit of a + pain as the validator is a java application. The action script is + brutal so far but it works. + </p> + <h3>.dillo/actions/smolcheck.sh</h3> + <pre><code>#!/bin/sh + +#smolweb validator location +smp="$HOME/src/smolweb-validator" + +# validate +cd $smp +output="$(java -cp "jsoup-1.21.2.jar:." SmolwebValidator $url --verbose)" +echo "$output" | dilloc rawload +</code></pre> + <p> + The action is added to ~/.dillo/dillorc and appears in Dillo's + right-click menu + </p> + <pre> +page_action="Smolcheck:~/.dillo/actions/smolcheck.sh" +</pre + > + <p> + see + <a href="https://codeberg.org/smolweb/smolweb-validator" + >https://codeberg.org/smolweb/smolweb-validator</a + > + </p> + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/2026/upgrade-to-dwm-6.8.html b/articles/2026/upgrade-to-dwm-6.8.html new file mode 100644 index 0000000..1637f7c --- /dev/null +++ b/articles/2026/upgrade-to-dwm-6.8.html @@ -0,0 +1,49 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>upgrade to dwm 6.8</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/index.php">Back</a> + </header> + <main> + <article> + <h2>upgrade to dwm 6.8</h2> + <h3>2026-06-29</h3> + <h4>dwm with some patches</h4> + <p> + I've been using dwm 6.5 for a while now, so I thought it was about + time I upgraded to dwm 6.8. + </p> + <p> + Ideally I'd settle for a horizontal scrolling wm as my Thinkpad screen + is small, but after flirting with nwm I'm staying with dwm for now. + </p> + <p> + I've added a few patches and shortcuts that make it easy for me to + circulate between windows and workspaces. I haven't added the xrdb + patch this time, instead I added the colorbar patch which nicely + colours the dwmblocks status bar and keeps things simple. Many of the + tiling patches I added to 6.5 have been left out as I just don't use + them. On a small 1366x768 screen splitting it renders it useless so + instead I use the monocle layout by default and switch workspaces with + super+right and left, and switch windows with super+up and down. + </p> + <p> + <a href="https://git.wittamore.fr/dwm-6.8-patched/tree" + >It's up on my git server if you are interested.</a + > + </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/articles/test.html b/articles/test.html new file mode 100755 index 0000000..461b48a --- /dev/null +++ b/articles/test.html @@ -0,0 +1,3 @@ + + +<!-- article end --><p> </p><p>§</p></body></html> diff --git a/deltachat.html b/deltachat.html new file mode 100644 index 0000000..4443a94 --- /dev/null +++ b/deltachat.html @@ -0,0 +1,22 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta + http-equiv="refresh" + content="0; URL=https://i.delta.chat/#ED59E209CCA23D16F40EB7F4AC0F8F0CFE934057&v=3&i=6G6xPiCMaT--yhMyn4r_1q-G&s=GB9pE6SoqzUpITR3XffnKQ1J&a=scu9zmbso%40nine.testrun.org&n=Philip+Wittamore" + /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + <title>Deltachat Contact</title> + </head> + <body> + <p> + <a + href="https://i.delta.chat/#ED59E209CCA23D16F40EB7F4AC0F8F0CFE934057&v=3&i=6G6xPiCMaT--yhMyn4r_1q-G&s=GB9pE6SoqzUpITR3XffnKQ1J&a=scu9zmbso%40nine.testrun.org&n=Philip+Wittamore" + >Contact me via Deltachat</a + > + </p> + </body> +</html> diff --git a/favicon-32x32.png b/favicon-32x32.png Binary files differnew file mode 100644 index 0000000..20f3fcb --- /dev/null +++ b/favicon-32x32.png diff --git a/favicon.ico b/favicon.ico Binary files differnew file mode 100644 index 0000000..960b017 --- /dev/null +++ b/favicon.ico diff --git a/feeds/fediverse-philip/feed b/feeds/fediverse-philip/feed new file mode 100644 index 0000000..0c5f064 --- /dev/null +++ b/feeds/fediverse-philip/feed @@ -0,0 +1 @@ +https://social.wittamore.fr/philip.rss diff --git a/feeds/fediverse-philip/title b/feeds/fediverse-philip/title new file mode 100644 index 0000000..0ad535d --- /dev/null +++ b/feeds/fediverse-philip/title @@ -0,0 +1 @@ +fediverse - philip@social.wittamore.fr diff --git a/feeds/gemini-vigrey.com/feed b/feeds/gemini-vigrey.com/feed new file mode 100644 index 0000000..cb2806e --- /dev/null +++ b/feeds/gemini-vigrey.com/feed @@ -0,0 +1 @@ +gemini://vigrey.com/journal/feed.rss diff --git a/feeds/gemini-vigrey.com/index.php b/feeds/gemini-vigrey.com/index.php new file mode 100644 index 0000000..a3a7d91 --- /dev/null +++ b/feeds/gemini-vigrey.com/index.php @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="gemini://vigrey.com/journal/feed.rss">gemini://vigrey.com/journal/feed.rss</a> (10 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="http://vigrey.com/journal/2025-comes-to-a-close.html">2025-12-31 2025 Comes to a Close</a></td></tr> +<tr><td>→</td><td><a href="http://vigrey.com/journal/my-first-game-for-the-playdate-flatcube.html">2025-11-19 My First Game for the Playdate - FlatCube</a></td></tr> +<tr><td>→</td><td><a href="http://vigrey.com/journal/winter-nights-2025.html">2025-10-06 Winter Nights 2025</a></td></tr> +<tr><td>→</td><td><a href="http://vigrey.com/journal/happy-equinox.html">2025-09-23 Happy Equinox!</a></td></tr> +<tr><td>→</td><td><a href="http://vigrey.com/journal/sundial-created-oaths-update-3.html">2025-09-14 Sundial Created - Oaths Update 3</a></td></tr> +<tr><td>→</td><td><a href="http://vigrey.com/journal/i-ordered-a-playdate-console.html">2025-09-10 I Ordered a Playdate Console</a></td></tr> +<tr><td>→</td><td><a href="http://vigrey.com/journal/the-magic-that-scares-me.html">2025-08-09 The Magic That Scares Me</a></td></tr> +<tr><td>→</td><td><a href="http://vigrey.com/journal/using-fceux-to-play-this-proof-of-concept-nes-game-is-a-user-input.html">2025-07-13 Using FCEUX to Play this Proof-of-Concept NES Game is a User Input - Expanding on PoC||GTFO 18:4</a></td></tr> +<tr><td>→</td><td><a href="http://vigrey.com/journal/no-online-june.html">2025-05-28 No Online June</a></td></tr> +<tr><td>→</td><td><a href="http://vigrey.com/journal/a-call-for-help-zim-file-expertise.html">2025-05-21 A Call for Help - ZIM File Expertise</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/gemini-vigrey.com/title b/feeds/gemini-vigrey.com/title new file mode 100644 index 0000000..00d6d69 --- /dev/null +++ b/feeds/gemini-vigrey.com/title @@ -0,0 +1 @@ +gemini - vigrey.com diff --git a/feeds/gopher-cosmic-voyage/feed b/feeds/gopher-cosmic-voyage/feed new file mode 100644 index 0000000..a51c1ef --- /dev/null +++ b/feeds/gopher-cosmic-voyage/feed @@ -0,0 +1 @@ +gopher://cosmic.voyage/0/rss.xml diff --git a/feeds/gopher-cosmic-voyage/index.php b/feeds/gopher-cosmic-voyage/index.php new file mode 100644 index 0000000..8f99cc1 --- /dev/null +++ b/feeds/gopher-cosmic-voyage/index.php @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="gopher://cosmic.voyage/0/rss.xml">gopher://cosmic.voyage/0/rss.xml</a> (19 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/APETLUNE%20ABYSS/SHIPLOG1.txt">2025-12-17 APETLUNE ABYSS - Mission Log</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/YZJ-ZX-17/sofia_petrova_1.txt">2025-11-20 YZJ-ZX-17 - Sofia Petrova 1</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/Marauder/SUPPLY_UPDATE_009.txt">2025-11-04 Marauder - SUPPLY_UPDATE_009</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/DO-28631-S-89594/transmission_00002.txt">2025-10-25 DO-28631-S-89594 - Transmission 09732</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/AUDIO%20FREIGHTER%201/LOGDATE_3.txt">2025-10-16 AUDIO FREIGHTER 1 - LOGDATE_3</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/Marauder/RE_AUDIO_FREIGHTER_2.txt">2025-10-12 Marauder - RE: AUDIO FREIGHTER 1</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/AUDIO%20FREIGHTER%201/LOGDATE_2.txt">2025-10-11 AUDIO FREIGHTER 1 - LOGDATE_2</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/AUDIO%20FREIGHTER%201/RE_APETLUNE_CORPORATION.txt">2025-10-11 AUDIO FREIGHTER 1 - RE: APETLUNE CORPORATION</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/Apetlune%20Corporation/RE_Audio_Freighter_One_1.txt">2025-10-11 Apetlune Corporation - RE: Audio Freighter One</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/Apetlune%20Corporation/RE_Audio_Freighter_One_0.txt">2025-10-10 Apetlune Corporation - RE: Audio Freighter One</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/AUDIO%20FREIGHTER%201/RE_MARAUDER_0.txt">2025-10-10 AUDIO FREIGHTER 1 - RE: MARAUDER</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/Marauder/RE_AUDIO_FREIGHTER_1.txt">2025-10-10 Marauder - RE: AUDIO FREIGHTER 1</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/AUDIO%20FREIGHTER%201/LOGDATE_1.txt">2025-10-10 AUDIO FREIGHTER 1 - LOGDATE_1</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/AUDIO%20FREIGHTER%201/LOGDATE_0.txt">2025-10-09 AUDIO FREIGHTER 1 - LOGDATE_0</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/Sagan/1760025340.txt">2025-10-09 Sagan - 1760025340 - First Transmission</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/HRVST%20Farming%20Unit-59/status_update.txt">2025-10-07 HRVST Farming Unit-59 - Status update</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/Aegis/Aegis-QEC847293652841-7.12.1123.txt">2025-10-02 Aegis - Aegis - QEC847293652841 - 7.12.1123</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/HRVST%20Farming%20Unit-59/dog.txt">2025-09-25 HRVST Farming Unit-59 - Unusual findings</a></td></tr> +<tr><td>→</td><td><a href="gopher://cosmic.voyage:70/0/S-99%20Tekniko/comm_02.txt">2025-05-15 S-99 Tekniko - COMM_02.TXT</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/gopher-cosmic-voyage/title b/feeds/gopher-cosmic-voyage/title new file mode 100644 index 0000000..9984e8b --- /dev/null +++ b/feeds/gopher-cosmic-voyage/title @@ -0,0 +1 @@ +gopher - Cosmic Voyage diff --git a/feeds/gopher-lfa/feed b/feeds/gopher-lfa/feed new file mode 100644 index 0000000..d504924 --- /dev/null +++ b/feeds/gopher-lfa/feed @@ -0,0 +1 @@ +gopher://lucio.albenga.es/0/lfa/en/phlog/rss.xml diff --git a/feeds/gopher-lfa/index.php b/feeds/gopher-lfa/index.php new file mode 100644 index 0000000..d0d8cfd --- /dev/null +++ b/feeds/gopher-lfa/index.php @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="gopher://lucio.albenga.es/0/lfa/en/phlog/rss.xml">gopher://lucio.albenga.es/0/lfa/en/phlog/rss.xml</a> (10 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="gopher://lucio.albenga.es:70/0/lfa/en/phlog/2026/0117-why_i_switched_back_from_emacs_to_vim.txt">2026-01-17 Why I switched back from EMACS to VIM?</a></td></tr> +<tr><td>→</td><td><a href="gopher://lucio.albenga.es:70/0/lfa/en/phlog/2025/1214-liferea_now_supports_gopher.txt">2025-12-14 Liferea Now Supports Gopher</a></td></tr> +<tr><td>→</td><td><a href="gopher://lucio.albenga.es:70/0/lfa/en/phlog/2025/1212-interview_with_mark_p_mccahill.txt">2025-12-12 Gopher Archaeology: Interview With Mark P. McCahill</a></td></tr> +<tr><td>→</td><td><a href="gopher://lucio.albenga.es:70/0/lfa/en/phlog/2025/1211-four_months_on_christmas.txt">2025-12-11 Four Months on Christmas</a></td></tr> +<tr><td>→</td><td><a href="gopher://lucio.albenga.es:70/0/lfa/en/phlog/2025/1201-setting_up_a_gcc_toolchain_for_programming_arduino_in_plain_c.txt">2025-12-01 Setting Up a GCC Toolchain For Programming Arduino In Plain C</a></td></tr> +<tr><td>→</td><td><a href="gopher://lucio.albenga.es:70/0/lfa/en/phlog/2025/1127-setting_up_the_fn_keys_of_a_thinkpad_t430_on_freebsd.txt">2025-11-27 Setting Up The Fn Keys of a Thinkpad T430 on FreeBSD</a></td></tr> +<tr><td>→</td><td><a href="gopher://lucio.albenga.es:70/0/lfa/en/phlog/2025/1126-changes_on_my_mail.txt">2025-11-26 Changes On My Email</a></td></tr> +<tr><td>→</td><td><a href="gopher://lucio.albenga.es:70/0/lfa/en/phlog/2025/1116-free_as_in_freedomn_versions_of_devuan_and_debian.txt">2025-11-16 100% "Free as in Freedom" Versions of Devuan and Debian</a></td></tr> +<tr><td>→</td><td><a href="gopher://lucio.albenga.es:70/0/lfa/en/phlog/2025/1114-gopher_archaeology.txt">2025-11-14 Gopher Archaeology</a></td></tr> +<tr><td>→</td><td><a href="gopher://lucio.albenga.es:70/0/lfa/en/phlog/2025/1113-ben_collvers_coprolit_for_gopher_and_fossil_scm.txt">2025-11-13 Ben Collver's Coprolit For Gopher & Fossil SCM</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/gopher-lfa/title b/feeds/gopher-lfa/title new file mode 100644 index 0000000..b715583 --- /dev/null +++ b/feeds/gopher-lfa/title @@ -0,0 +1 @@ +gopher - LFA's phlog diff --git a/feeds/gopher-mattos/feed b/feeds/gopher-mattos/feed new file mode 100644 index 0000000..6e29926 --- /dev/null +++ b/feeds/gopher-mattos/feed @@ -0,0 +1 @@ +gopher://box.matto.nl/0/gopherindex.rss diff --git a/feeds/gopher-mattos/index.php b/feeds/gopher-mattos/index.php new file mode 100644 index 0000000..9ecad01 --- /dev/null +++ b/feeds/gopher-mattos/index.php @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="gopher://box.matto.nl/0/gopherindex.rss">gopher://box.matto.nl/0/gopherindex.rss</a> (5 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="gopher://box.matto.nl:70/0/lisp-is-fun.txt">2026-01-23 Lisp is fun</a></td></tr> +<tr><td>→</td><td><a href="gopher://box.matto.nl:70/0/digital-ad-spending-usd-710-billion-in-2026.txt">2026-01-05 Digital ad spending USD 710 billion in 2026</a></td></tr> +<tr><td>→</td><td><a href="gopher://box.matto.nl:70/0/textonly-websites.txt">2026-01-03 Text-only websites</a></td></tr> +<tr><td>→</td><td><a href="gopher://box.matto.nl:70/0/books-i-enjoyed-reading-in-2025.txt">2026-01-02 Books I enjoyed reading in 2025</a></td></tr> +<tr><td>→</td><td><a href="gopher://box.matto.nl:70/0/januari-questions-of-christina.txt">2026-01-01 Januari Questions of Christina</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/gopher-mattos/title b/feeds/gopher-mattos/title new file mode 100644 index 0000000..767f557 --- /dev/null +++ b/feeds/gopher-mattos/title @@ -0,0 +1 @@ +gopher - Mattos Phlog diff --git a/feeds/gopher-wittamore/feed b/feeds/gopher-wittamore/feed new file mode 100644 index 0000000..2f381c1 --- /dev/null +++ b/feeds/gopher-wittamore/feed @@ -0,0 +1 @@ +gopher://wittamore.fr/0/phlog/rss.xml diff --git a/feeds/gopher-wittamore/index.html b/feeds/gopher-wittamore/index.html new file mode 100644 index 0000000..8d82d9e --- /dev/null +++ b/feeds/gopher-wittamore/index.html @@ -0,0 +1,14 @@ +<html> +<header> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <meta name="description" content="Gopher without the crap" > + <link href="/.theme/style.css" rel="stylesheet" type="text/css" > + <link rel="icon" href="/favicon.ico" type="image/x-icon" > + <link rel="icon" href="/favicon-64x64.png" type="image/png" > + <title>Toothless</title> +</header> +<body> +<ul> +<p>RSS feed : gopher://spike.nagatha.fr/0/phlog/rss.xml</p> +<li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-12-15-22-10-Re.-The-even-smoller-web.txt">2025-12-15 Re. The even smoller web</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-12-12-10-10-Note-to-self-dr3-not-supported-on-X270-solved.txt">2025-12-12 Note to self: dr3 not supported on X270: solved</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-12-11-14-02-Protonmail-bridge.txt">2025-12-11 Protonmail bridge</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-12-10-18-06-Protonmail.txt">2025-12-10 Protonmail</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-12-07-16-04-The-even-smoller-web.txt">2025-12-07 The even smoller web</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-12-06-09-09-Artix-and-dinit-wpa_supplicant.txt">2025-12-06 Artix and dinit-wpa_supplicant</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-12-05-10-10-mastodon-reply-about-gopher.txt">2025-12-05 mastodon reply about gopher</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-12-04-10-10-DNS-blocklist-for-all-computers.txt">2025-12-04 DNS blocklist for all computers</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-12-03-18-06-Putting-off-a-RAM-upgrade.txt">2025-12-03 Putting off a RAM upgrade</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-12-02-16-04-MPV-error-playing-youtube-videos-on-Artix-Linux.txt">2025-12-02 MPV error playing youtube videos on Artix Linux</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-30-07-07-A-Foray-into-FreeBSD.txt">2025-11-30 A Foray into FreeBSD</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-26-06-06-where-is-the-scroll-lock-key-on-my-X270.txt">2025-11-26 where is the scroll lock key on my X270</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-24-14-02-Update.txt">2025-11-24 Update</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-21-14-02-re.-Ideal.txt">2025-11-21 re. Ideal</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-20-07-07-An-ideal.txt">2025-11-20 An ideal</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-18-10-10-True-lies.txt">2025-11-18 True lies</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-17-08-08-PHP-and-gophernicus.txt">2025-11-17 PHP and gophernicus</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-15-23-11-Update-regarding-UTF-8.txt">2025-11-15 Update regarding UTF-8</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-15-12-12-peer-to-peer-chat-solutions.txt">2025-11-15 peer to peer chat solutions</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-14-12-12-This-gopher-hole-uses-UTF-8.txt">2025-11-14 This gopher hole uses UTF-8</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-13-08-08-Ok-directories-need-indicating.txt">2025-11-13 Ok, directories need indicating</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-12-12-12-on-media-mime-types.txt">2025-11-12 on media (mime) types</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-11-11-11-11th-november---rememberance-day.txt">2025-11-11 11th november - rememberance day</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-11-07-07-ChatGPT-tells-me-about-Gopher-selectors.txt">2025-11-11 ChatGPT tells me about Gopher selectors</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-09-19-07-Gopher-Chawan-and-Markdown-files.txt">2025-11-09 Gopher, Chawan and Markdown files</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-09-06-06-Playing-with-gopher-selectors.txt">2025-11-09 Playing with gopher selectors</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-06-07-07-Gopher.fr.txt">2025-11-06 Gopher.fr</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-05-09-09-Kitty-and-utf-8-icons.txt">2025-11-05 Kitty and utf-8 icons</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-04-22-10-Translating-Gopher-text-with-Chawan.txt">2025-11-04 Translating Gopher text with Chawan</a></li><li><a href="gopher://spike.nagatha.fr:70/0/phlog/2025/2025-11-04-20-08-Windowmaker-in-1998.txt">2025-11-04 Windowmaker in 1998</a></li></ul></body></html>
\ No newline at end of file diff --git a/feeds/gopher-wittamore/index.php b/feeds/gopher-wittamore/index.php new file mode 100644 index 0000000..410faf4 --- /dev/null +++ b/feeds/gopher-wittamore/index.php @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="gopher://spike.nagatha.fr/0/phlog/rss.xml">gopher://spike.nagatha.fr/0/phlog/rss.xml</a> (5 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="gopher://spike.nagatha.fr:70/0/phlog/2026/2026-01-24-09-09-A-gopher-server-in-Bash.txt">2026-01-24 A gopher server in Bash</a></td></tr> +<tr><td>→</td><td><a href="gopher://spike.nagatha.fr:70/0/phlog/2026/2026-01-23-14-02-Xdm-simple-theme-for-dinit.txt">2026-01-23 Xdm simple theme for dinit</a></td></tr> +<tr><td>→</td><td><a href="gopher://spike.nagatha.fr:70/0/phlog/2026/2026-01-23-08-08-Back-to-DWM.txt">2026-01-23 Back to DWM</a></td></tr> +<tr><td>→</td><td><a href="gopher://spike.nagatha.fr:70/0/phlog/2026/2026-01-19-15-03-Bash-language-server-for-Micro-editor.txt">2026-01-19 Bash language server for Micro editor</a></td></tr> +<tr><td>→</td><td><a href="gopher://spike.nagatha.fr:70/0/phlog/2026/2026-01-01-10-10-Using-Xfce4-with-Devilspie2.txt">2026-01-01 Using Xfce4 with Devilspie2</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/gopher-wittamore/title b/feeds/gopher-wittamore/title new file mode 100644 index 0000000..c8818b6 --- /dev/null +++ b/feeds/gopher-wittamore/title @@ -0,0 +1 @@ +gopher - Phil's Phlog diff --git a/feeds/http-20minutes/feed b/feeds/http-20minutes/feed new file mode 100644 index 0000000..d2c3ed8 --- /dev/null +++ b/feeds/http-20minutes/feed @@ -0,0 +1,2 @@ +https://www.20minutes.fr/feeds/rss-bretagne.xml + diff --git a/feeds/http-20minutes/index.php b/feeds/http-20minutes/index.php new file mode 100644 index 0000000..ed3d92d --- /dev/null +++ b/feeds/http-20minutes/index.php @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://www.20minutes.fr/feeds/rss-bretagne.xml">https://www.20minutes.fr/feeds/rss-bretagne.xml</a> (20 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://www.20minutes.fr/societe/4197260-20260125-municipales-2026-responsabilites-difficiles-peu-habitants-villes-vont-elles-finir-candidats?at_medium=display&at_campaign=149">2026-01-25 Municipales 2026 : Responsabilités difficiles, peu d’habitants… Des villes vont-elles finir sans candidats ?</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/societe/4197849-20260125-meteo-france-finistere-morbihan-ille-vilaine-restent-vigilance-orange-crues?at_medium=display&at_campaign=149">2026-01-25 Météo-France : Finistère, Morbihan et Ille-et-Vilaine restent en vigilance orange crues</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/faits_divers/faits-divers-rennes/4197826-20260124-bretagne-appelle-depuis-coffre-secours-quatre-hommes-incarceres-enlevement?at_medium=display&at_campaign=149">2026-01-24 Bretagne : Elle appelle depuis le coffre au secours, quatre hommes incarcérés pour enlèvement</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/planete/4197784-20260124-depression-ingrid-bretagne-reste-alerte-vigilance-crues?at_medium=display&at_campaign=149">2026-01-24 Dépression Ingrid : La Bretagne reste en alerte vigilance aux crues</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/societe/4197576-20260123-vigilance-orange-crues-bretagne-redoute-arrivee-tempete-ingrid?at_medium=display&at_campaign=149">2026-01-23 En vigilance orange aux crues, la Bretagne redoute l’arrivée de la tempête Ingrid</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/faits_divers/4197633-20260123-bretagne-caravanes-maisons-luxe-armes-fraude-tva-million-euros-demantelee?at_medium=display&at_campaign=149">2026-01-23 Bretagne : Caravanes, maisons de luxe, armes… Une fraude à la TVA d’un million d’euros démantelée</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/justice/4197655-20260123-bretagne-accro-cocaine-femme-frappe-mari-surprise-lit-dealer?at_medium=display&at_campaign=149">2026-01-23 Bretagne : Accro à la cocaïne, une femme frappe son mari qui l’avait surprise au lit avec son dealer</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/justice/4197592-20260123-rennes-envie-rentrer-evade-planetarium-ecope-deux-ans-prison-plus?at_medium=display&at_campaign=149">2026-01-23 Rennes : « Je n’avais pas envie de rentrer »… L’évadé du planétarium écope de deux ans de prison (de plus)</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/societe/4197579-20260123-vigilance-orange-crues-bretagne-redoute-arrivee-tempete-ingrid?at_medium=display&at_campaign=149">2026-01-23 En vigilance orange aux crues, la Bretagne redoute l’arrivée de la tempête Ingrid</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/faits_divers/4197375-20260122-bateau-derive-mediterranee-apres-operation-trente-heures-seul-naufrage-pu-etre-recupere?at_medium=display&at_campaign=149">2026-01-22 Bateau à la dérive en Méditerranée : Après une opération de trente heures, un seul naufragé a pu être récupéré</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/societe/4197362-20260122-bretagne-route-effondree-maisons-inondees-vigilance-orange-prolongee?at_medium=display&at_campaign=149">2026-01-22 Bretagne : Route effondrée, maisons inondées… Vigilance orange dans le Finistère, l'Ille-et-Vilaine et le Morbihan</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/justice/4197243-20260121-mort-allan-lambin-saint-malo-complique-nuit-garde-vue-defendent-avocats-policiers?at_medium=display&at_campaign=149">2026-01-21 Mort d’Allan Lambin à Saint-Malo : « C’est compliqué la nuit en garde à vue », défendent les avocats des policiers</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/faits_divers/4197255-20260121-morbihan-proprietaire-condamne-apres-etre-introduit-chez-locataire-absence?at_medium=display&at_campaign=149">2026-01-21 Morbihan : Un propriétaire condamné après s’être introduit chez sa locataire en son absence</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/justice/4197259-20260121-proces-scouarnec-environ-180-victimes-fixees-indemnisations-ancien-chirurgien-pourra-payer?at_medium=display&at_campaign=149">2026-01-21 Procès Le Scouarnec : Environ 180 victimes fixées sur leurs indemnisations (que l’ancien chirurgien ne pourra pas payer)</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/justice/4197205-20260121-bretagne-deux-religieuses-tuees-roulaient-tres-lentement-quand-elles-percutees?at_medium=display&at_campaign=149">2026-01-21 Bretagne : Les deux religieuses tuées roulaient à 15 km/h quand elles ont été percutées</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/planete/4197173-20260121-meteo-deux-departements-bretagne-alerte-orange-pluie-inondation?at_medium=display&at_campaign=149">2026-01-21 Météo : Deux départements de Bretagne en alerte orange pluie-inondation</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/justice/4197130-20260120-mort-allan-lambin-garde-vue-commis-fautes-graves-sursis-requis-contre-quatre-policiers?at_medium=display&at_campaign=149">2026-01-20 Mort d’Allan Lambin en garde à vue : « Ils ont commis des fautes graves »… Du sursis requis contre les quatre policiers</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/justice/4197127-20260120-affaire-scouarnec-justice-reconnait-premiere-fois-prejudice-crime-seriel-victimes?at_medium=display&at_campaign=149">2026-01-20 Affaire Le Scouarnec : La justice reconnaît pour la première fois un préjudice de crime sériel aux victimes</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/justice/4197050-20260120-mort-allan-lambin-commissariat-saint-malo-intervention-plus-rapide-permis-sauver?at_medium=display&at_campaign=149">2026-01-20 Mort d’Allan Lambin au commissariat de Saint-Malo : Une intervention plus rapide aurait-elle permis de le sauver ?</a></td></tr> +<tr><td>→</td><td><a href="https://www.20minutes.fr/planete/4196991-20260120-intemperies-aude-herault-restent-alerte-orange-morbihan-finistere-mercredi?at_medium=display&at_campaign=149">2026-01-20 Intempéries : L’Aude et l’Hérault restent en alerte orange, le Morbihan et le Finistère le seront mercredi</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/http-20minutes/title b/feeds/http-20minutes/title new file mode 100644 index 0000000..c2622ac --- /dev/null +++ b/feeds/http-20minutes/title @@ -0,0 +1 @@ +http - 20minutes Bretagne diff --git a/feeds/http-adele/feed b/feeds/http-adele/feed new file mode 100644 index 0000000..7c3ce3b --- /dev/null +++ b/feeds/http-adele/feed @@ -0,0 +1 @@ +https://adele.pages.casa/md/blog/rss.xml diff --git a/feeds/http-adele/index.php b/feeds/http-adele/index.php new file mode 100644 index 0000000..e923cbd --- /dev/null +++ b/feeds/http-adele/index.php @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://adele.pages.casa/md/blog/rss.xml">https://adele.pages.casa/md/blog/rss.xml</a> (6 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://adele.pages.casa/md/blog/a-mailbox-for-christmas.md">2025-12-18 A mailbox for Christmas</a></td></tr> +<tr><td>→</td><td><a href="https://adele.pages.casa/md/blog/preparing-a-new-mail-xmpp-service.md">2025-11-04 Preparing a new mail and xmpp service</a></td></tr> +<tr><td>→</td><td><a href="https://adele.pages.casa/md/blog/excited-to-annouce-writes.casa.md">2025-09-01 Excited to announce writes.casa</a></td></tr> +<tr><td>→</td><td><a href="https://adele.pages.casa/md/blog/smolweb_vs_smallweb.md">2025-08-12 The difference between "smolweb" and "small web"</a></td></tr> +<tr><td>→</td><td><a href="https://adele.pages.casa/md/blog/why-I-prefer-human-readable-file-formats.md">2025-08-04 Why I prefer human-readable file formats</a></td></tr> +<tr><td>→</td><td><a href="https://adele.pages.casa/md/blog/the-charm-of-ascii-smileys.md">2025-07-19 The charm of ASCII smileys :-)</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/http-adele/title b/feeds/http-adele/title new file mode 100644 index 0000000..8185fd5 --- /dev/null +++ b/feeds/http-adele/title @@ -0,0 +1 @@ +http - Adele diff --git a/feeds/http-bloggings/feed b/feeds/http-bloggings/feed new file mode 100644 index 0000000..f13a8ee --- /dev/null +++ b/feeds/http-bloggings/feed @@ -0,0 +1 @@ +https://wittamore.fr/rss.xml diff --git a/feeds/http-bloggings/title b/feeds/http-bloggings/title new file mode 100644 index 0000000..f3719a4 --- /dev/null +++ b/feeds/http-bloggings/title @@ -0,0 +1 @@ +This blog diff --git a/feeds/http-eff/feed b/feeds/http-eff/feed new file mode 100644 index 0000000..9518f49 --- /dev/null +++ b/feeds/http-eff/feed @@ -0,0 +1 @@ +https://www.eff.org/rss/updates.xml diff --git a/feeds/http-eff/index.php b/feeds/http-eff/index.php new file mode 100644 index 0000000..703d15a --- /dev/null +++ b/feeds/http-eff/index.php @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://www.eff.org/rss/updates.xml">https://www.eff.org/rss/updates.xml</a> (30 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/search-engines-ai-and-long-fight-over-fair-use">2026-01-24 Search Engines, AI, And The Long Fight Over Fair Use</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/rent-only-copyright-culture-makes-us-all-worse">2026-01-23 Rent-Only Copyright Culture Makes Us All Worse Off</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/copyright-kills-competition">2026-01-21 Copyright Kills Competition</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/copyright-should-not-enable-monopoly">2026-01-21 Copyright Should Not Enable Monopoly</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/statutory-damages-fuel-copyright-based-censorship">2026-01-20 Statutory Damages: The Fuel of Copyright-based Censorship</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/worst-data-breaches-2025-and-what-you-can-do-effector-381">2026-01-20 💾 The Worst Data Breaches of 2025—And What You Can Do | EFFector 38.1</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/eff-joins-47-internet-advocates-calling-iranian-government-restore-full-internet">2026-01-20 EFF Joins Internet Advocates Calling on the Iranian Government to Restore Full Internet Connectivity</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/eff-condemns-fbi-search-washington-post-reporters-home">2026-01-16 EFF Condemns FBI Search of Washington Post Reporter’s Home</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/eff-california-appeals-court-first-amendment-protects-journalist-tech-executives">2026-01-16 EFF to California Appeals Court: First Amendment Protects Journalist from Tech Executive’s Meritless Lawsuit</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/baton-rouge-acquires-straight-military-surveillance-drone">2026-01-16 Baton Rouge Acquires a Straight-Up Military Surveillance Drone</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/congress-wants-hand-your-parenting-big-tech">2026-01-16 Congress Wants To Hand Your Parenting to Big Tech</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/report-ice-using-palantir-tool-feeds-medicaid-data">2026-01-15 Report: ICE Using Palantir Tool That Feeds On Medicaid Data</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/so-youve-hit-age-gate-what-now">2026-01-14 So, You’ve Hit an Age Gate. What Now?</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/how-hackers-are-fighting-back-against-ice">2026-01-08 How Hackers Are Fighting Back Against ICE</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/ice-going-surveillance-shopping-spree">2026-01-07 ICE Is Going on a Surveillance Shopping Spree</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2026/01/effecting-change-human-cost-online-age-verification">2026-01-06 EFFecting Change: The Human Cost of Online Age Verification</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/homeland-security-spending-trail-how-follow-money-through-us-government-databases">2026-01-06 The Homeland Security Spending Trail: How to Follow the Money Through U.S. Government Databases</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/year-states-chose-surveillance-over-safety-2025-review">2026-01-02 The Year States Chose Surveillance Over Safety: 2025 in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/surveillance-self-defense-2025-year-review">2026-01-02 Surveillance Self-Defense: 2025 Year in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/congresss-crusade-age-gate-internet-2025-review">2025-12-31 Congress's Crusade to Age Gate the Internet: 2025 in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/states-tried-censor-kids-online-courts-and-eff-mostly-stopped-them-2025-review">2025-12-31 States Tried to Censor Kids Online. Courts, and EFF, Mostly Stopped Them: 2025 in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/site-blocking-laws-will-always-be-bad-idea-2025-review">2025-12-30 Site Blocking Laws Will Always Be a Bad Idea: 2025 in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/effs-investigations-expose-flock-safetys-surveillance-abuses-2025-review">2025-12-30 EFF's Investigations Expose Flock Safety's Surveillance Abuses: 2025 in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/fighting-renewed-attempts-make-isps-copyright-cops-2025-review">2025-12-30 Fighting Renewed Attempts to Make ISPs Copyright Cops: 2025 in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/operations-security-opsec-trainings-2025-review">2025-12-29 Operations Security (OPSEC) Trainings: 2025 in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/eff-press-2025-review">2025-12-29 EFF in the Press: 2025 in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/drone-first-responder-programs-2025-review">2025-12-29 Drone as First Responder Programs: 2025 in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/effector-audio-speaks-our-rights-2025-year-review">2025-12-28 EFFector Audio Speaks Up for Our Rights: 2025 Year in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/procurement-power-when-cities-realized-they-can-just-say-no-2025-review">2025-12-28 Procurement Power—When Cities Realized They Can Just Say No: 2025 in Review</a></td></tr> +<tr><td>→</td><td><a href="https://www.eff.org/deeplinks/2025/12/defending-encryption-us-and-abroad-2025-review">2025-12-28 Defending Encryption in the U.S. and Abroad: 2025 in Review</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/http-eff/title b/feeds/http-eff/title new file mode 100644 index 0000000..531a8bb --- /dev/null +++ b/feeds/http-eff/title @@ -0,0 +1 @@ +http - Electronic Frontier Foundation diff --git a/feeds/http-petergartner/feed b/feeds/http-petergartner/feed new file mode 100644 index 0000000..f4742b7 --- /dev/null +++ b/feeds/http-petergartner/feed @@ -0,0 +1 @@ +https://petergarner.net/feed.rss diff --git a/feeds/http-petergartner/index.php b/feeds/http-petergartner/index.php new file mode 100644 index 0000000..8e46123 --- /dev/null +++ b/feeds/http-petergartner/index.php @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://petergarner.net/feed.rss">https://petergarner.net/feed.rss</a> (15 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20251228-The+Derbyshire+Mini-Bash.html">2025-12-28 The Derbyshire Mini-Bash</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20251026-Mission+Northumberland+-+Epilogue.html">2025-10-26 Mission Northumberland - Epilogue</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20251008-Mission+Northumberland+-+Wednesday.html">2025-10-08 Mission Northumberland - Wednesday</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20251007-Mission+Northumberland+-+Tuesday.html">2025-10-07 Mission Northumberland - Tuesday</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20251006-Mission+Northumberland+-+Monday.html">2025-10-06 Mission Northumberland - Monday</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20251005-Mission+Northumberland+-+Sunday.html">2025-10-05 Mission Northumberland - Sunday</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20250714-The+Times+They+Are+a-changing+-+Part+3.html">2025-07-14 The Times They Are a-changing - Part 3</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20250527-The+Times+They+Are+a-changing+-+Part+2.html">2025-05-27 The Times They Are a-changing - Part 2</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20250526-Walking+the+Walk.html">2025-05-26 Walking the Walk</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20250521-The+Times+They+Are+a-changing+-+Part+1.html">2025-05-21 The Times They Are a-changing - Part 1</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20250130-Q1+Update+-+better+late+than+never.html">2025-01-30 Q1 Update - better late than never</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20241017-We+need+to+talk+about+bread.html">2024-10-17 We need to talk about bread</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20240926-Drifting+Back+to+the+Dark+Side.html">2024-09-26 Drifting Back to the Dark Side</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20240101-Sod+Off+2023+and+Welcome+2024.html">2024-01-01 Sod Off 2023 and Welcome 2024</a></td></tr> +<tr><td>→</td><td><a href="https://petergarner.net/notes.php?thisnote=20230727-Back+on+the+road+again.html">2023-07-27 Back on the road again</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/http-petergartner/title b/feeds/http-petergartner/title new file mode 100644 index 0000000..4e39fc2 --- /dev/null +++ b/feeds/http-petergartner/title @@ -0,0 +1,2 @@ +http - PeterGartner + diff --git a/feeds/http-pluralistic/feed b/feeds/http-pluralistic/feed new file mode 100644 index 0000000..e3813a0 --- /dev/null +++ b/feeds/http-pluralistic/feed @@ -0,0 +1 @@ +https://pluralistic.net/feed/ diff --git a/feeds/http-pluralistic/index.php b/feeds/http-pluralistic/index.php new file mode 100644 index 0000000..bba6701 --- /dev/null +++ b/feeds/http-pluralistic/index.php @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://pluralistic.net/feed/">https://pluralistic.net/feed/</a> (10 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://pluralistic.net/2026/01/22/optimized-for-unoptimizability/">2026-01-22 Pluralistic: The petty (but undeniable) delights of cultivating unoptimizability as a habit (22 Jan 2026)</a></td></tr> +<tr><td>→</td><td><a href="https://pluralistic.net/2026/01/21/cod-marxism/">2026-01-21 Pluralistic: Google's AI pricing plan (21 Jan 2026)</a></td></tr> +<tr><td>→</td><td><a href="https://pluralistic.net/2026/01/20/i-would-prefer-not-to/">2026-01-20 Pluralistic: AI is how bosses wage war on "professions" (20 Jan 2026)</a></td></tr> +<tr><td>→</td><td><a href="https://pluralistic.net/2026/01/19/billionaire-solipsism/">2026-01-19 Pluralistic: Social media without socializing (19 Jan 2026)</a></td></tr> +<tr><td>→</td><td><a href="https://pluralistic.net/2026/01/17/erin-lets-go/">2026-01-17 Pluralistic: The world needs an Ireland for disenshittification (17 Jan 2026)</a></td></tr> +<tr><td>→</td><td><a href="https://pluralistic.net/2026/01/16/interrupt-driven/">2026-01-16 Pluralistic: Catch this! (16 Jan 2026)</a></td></tr> +<tr><td>→</td><td><a href="https://pluralistic.net/2026/01/15/how-the-light-gets-in/">2026-01-15 Pluralistic: How the Light Gets In (15 Jan 2026)</a></td></tr> +<tr><td>→</td><td><a href="https://pluralistic.net/2026/01/14/sole-and-despotic/">2026-01-14 Pluralistic: It's not normal (14 Jan 2026)</a></td></tr> +<tr><td>→</td><td><a href="https://pluralistic.net/2026/01/13/not-sorry/">2026-01-13 Pluralistic: Sorry, eh (13 Jan 2026)</a></td></tr> +<tr><td>→</td><td><a href="https://pluralistic.net/2026/01/12/disenshittification-nation-2/">2026-01-12 Pluralistic: A winning trade war strategy for Canada (11 Jan 2026)</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/http-pluralistic/title b/feeds/http-pluralistic/title new file mode 100644 index 0000000..9fafaae --- /dev/null +++ b/feeds/http-pluralistic/title @@ -0,0 +1 @@ +http - Pluralistic diff --git a/feeds/http-scitechdaily/feed b/feeds/http-scitechdaily/feed new file mode 100644 index 0000000..8b66ffe --- /dev/null +++ b/feeds/http-scitechdaily/feed @@ -0,0 +1 @@ +https://scitechdaily.com/feed/ diff --git a/feeds/http-scitechdaily/title b/feeds/http-scitechdaily/title new file mode 100644 index 0000000..65dfc09 --- /dev/null +++ b/feeds/http-scitechdaily/title @@ -0,0 +1 @@ +http - SciTechDaily diff --git a/feeds/http-smolweb/feed b/feeds/http-smolweb/feed new file mode 100644 index 0000000..18a4820 --- /dev/null +++ b/feeds/http-smolweb/feed @@ -0,0 +1 @@ +https://smolweb.org/atom.xml diff --git a/feeds/http-smolweb/index.php b/feeds/http-smolweb/index.php new file mode 100644 index 0000000..ab0a49b --- /dev/null +++ b/feeds/http-smolweb/index.php @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://smolweb.org/atom.xml">https://smolweb.org/atom.xml</a> (11 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2025-11-06-adding-semantic-table-elements.html">2025-11-06 Adding semantic table elements</a></td></tr> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2025-08-17-smolweb-validator.html">2025-08-17 Smolweb-validator</a></td></tr> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2025-04-20-preferred-subdomain.html">2025-04-20 `w.` is the preferred subdomain for smolweb sites</a></td></tr> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2025-04-20-smolweb-symbol.html">2025-04-20 New smolweb symbol could be a lowercase w between angle brackets in monospaced font</a></td></tr> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2024-07-22-simple-css.html">2024-07-22 Simple.css: a minimalist css framework</a></td></tr> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2024-06-15-allowing-javascript-in-smolweb.html">2024-06-15 Allowing JavaScript in smolweb</a></td></tr> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2023-12-23-adding-figure-tags.html">2023-12-23 Adding figure and figcaption tags</a></td></tr> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2023-10-16-wiby-a-search-engine-for-the-smolweb.html">2023-10-16 Wiby, a search engine for the smolweb</a></td></tr> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2023-10-11-smolweb-bash-generator.html">2023-10-11 A smolweb bash generator</a></td></tr> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2023-10-09-1st-post.html">2023-10-09 First post</a></td></tr> +<tr><td>→</td><td><a href="https://smolweb.org/posts/2023-10-09-an-other-smallweb-definition.html">2023-10-09 An other Small Web definition</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/http-smolweb/title b/feeds/http-smolweb/title new file mode 100644 index 0000000..b44d8c8 --- /dev/null +++ b/feeds/http-smolweb/title @@ -0,0 +1 @@ +http - Smolweb diff --git a/feeds/index.php b/feeds/index.php new file mode 100644 index 0000000..c524291 --- /dev/null +++ b/feeds/index.php @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p> last update: 2026-01-25 11:01:01</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>📁</td><td><a href="gemini-vigrey.com">gemini - vigrey.com</a></td></tr> +<tr><td>📁</td><td><a href="gopher-cosmic-voyage">gopher - Cosmic Voyage</a></td></tr> +<tr><td>📁</td><td><a href="gopher-lfa">gopher - LFA's phlog</a></td></tr> +<tr><td>📁</td><td><a href="gopher-mattos">gopher - Mattos Phlog</a></td></tr> +<tr><td>📁</td><td><a href="gopher-nagatha">gopher - Phil's Phlog</a></td></tr> +<tr><td>📁</td><td><a href="http-adele">http - Adele</a></td></tr> +<tr><td>📁</td><td><a href="http-eff">http - Electronic Frontier Foundation</a></td></tr> +<tr><td>📁</td><td><a href="http-petergartner">http - PeterGartner</a></td></tr> +<tr><td>📁</td><td><a href="http-pluralistic">http - Pluralistic</a></td></tr> +<tr><td>📁</td><td><a href="http-smolweb">http - Smolweb</a></td></tr> +<tr><td>📁</td><td><a href="news-20minutes">news - 20minutes Bretagne</a></td></tr> +<tr><td>📁</td><td><a href="news-euronews">news - Euronews</a></td></tr> +<tr><td>📁</td><td><a href="news-guardian-europe">news - The Guardian Europe</a></td></tr> +<tr><td>📁</td><td><a href="news-newscientist">news - New Scientist</a></td></tr> +<tr><td>📁</td><td><a href="youtube-breadonpenguins">youtube - Bread on Penguins</a></td></tr> +<tr><td>📁</td><td><a href="youtube-mental-outlaw">youtube - Mental Outlaw</a></td></tr> +<tr><td>📁</td><td><a href="youtube-naomi-brockwell">youtube - Naomi Brockwell</a></td></tr> +<tr><td>📁</td><td><a href="youtube-veronica-explains">youtube - Veronica explains</a></td></tr> +<tr><td>📁</td><td><a href="youtube-yousuckatprogramming">youtube - You suck at programming</a></td></tr> +</tbody> +</table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/news-euronews/feed b/feeds/news-euronews/feed new file mode 100644 index 0000000..917e1da --- /dev/null +++ b/feeds/news-euronews/feed @@ -0,0 +1 @@ +https://www.euronews.com/rss diff --git a/feeds/news-euronews/index.php b/feeds/news-euronews/index.php new file mode 100644 index 0000000..c1156a1 --- /dev/null +++ b/feeds/news-euronews/index.php @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://www.euronews.com/rss">https://www.euronews.com/rss</a> (30 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="http://www.euronews.com/next/2026/01/25/who-uses-ai-in-europe-and-where-its-still-taboo">2026-01-25 Who uses AI in Europe - and where it’s still taboo?</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/health/2026/01/25/women-lose-75-million-years-of-healthy-life-annually-yet-receive-only-6-of-health-funding">2026-01-25 Women lose 75 million years of healthy life annually – yet receive only 6% of health funding</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/travel/2026/01/25/this-year-could-be-a-surprisingly-affordable-time-to-see-japans-cherry-blossoms">2026-01-25 This year could be a surprisingly affordable time to see Japan’s cherry blossoms</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/video/2026/01/25/latest-news-bulletin-january-25th-2026-morning">2026-01-25 Latest news bulletin | January 25th, 2026 – Morning</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/green/2026/01/25/wolf-pack-forces-italian-airport-to-close-how-this-region-is-struggling-with-coexistence">2026-01-25 Wolf pack forces Italian airport to close: How this region is struggling with coexistence</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/video/2026/01/25/hundreds-gather-at-memorial-for-alex-pretti-the-man-killed-by-border-patrol-officer-in-min">2026-01-24 Hundreds gather at memorial for Alex Pretti, the man killed by Border Patrol officer in Minneapolis</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/video/2026/01/24/video-shows-confrontation-between-federal-agents-leading-up-to-a-fatal-shooting-in-minneap">2026-01-24 Video shows confrontation between federal agents leading up to a fatal shooting in Minneapolis</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/business/2026/01/24/trump-threatens-canada-with-100-tariff-over-its-trade-deal-with-china">2026-01-24 Trump threatens Canada with 100% tariff over its trade deal with China</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/video/2026/01/24/latest-news-bulletin-january-24th-2026-evening">2026-01-24 Latest news bulletin | January 24th, 2026 – Evening</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/2026/01/24/federal-officers-shoot-another-person-in-minneapolis-amid-immigration-crackdown-governor-s">2026-01-24 Federal officers shoot man dead in Minneapolis amid immigration crackdown</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/my-europe/2026/01/24/italy-recalls-swiss-ambassador-after-crans-montana-bar-fire-suspect-is-released">2026-01-24 Italy recalls Swiss ambassador after Crans-Montana bar fire suspect is released</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/2026/01/24/thousands-rally-and-hundreds-of-businesses-close-in-protest-against-ice-presence-in-minnes">2026-01-24 Thousands rally and hundreds of businesses close in protest against ICE presence in Minnesota</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/video/2026/01/24/united-states-empty-shelves-as-shoppers-race-to-stock-up-before-winter-storm">2026-01-24 United States: Empty shelves as shoppers race to stock up before winter storm</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/video/2026/01/24/latest-news-bulletin-january-24th-2026-midday">2026-01-24 Latest news bulletin | January 24th, 2026 – Midday</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/2026/01/24/russia-launches-massive-strikes-on-ukraines-largest-cities-and-targets-energy-infrastructu">2026-01-24 Ukraine facing emergency power cuts after Russian strikes hit energy infrastructure</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/my-europe/2026/01/24/green-electricity-which-eu-countries-are-using-the-most">2026-01-24 Green electricity: Which EU countries are using the most?</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/2026/01/24/abu-dhabi-hosts-russia-ukraine-peace-talks-with-territorial-issues-a-priority">2026-01-24 More talks expected next week after Ukraine, Russia and US conclude Abu Dhabi meeting</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/video/2026/01/24/denmark-pm-mette-frederiksen-visits-greenland-for-show-of-support">2026-01-24 Denmark PM Mette Frederiksen visits Greenland for 'show of support'</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/culture/2026/01/24/straight-from-the-heart-love-letters-exhibition-examines-500-years-of-emotions">2026-01-24 Straight from the heart: 'Love Letters' exhibition examines 500 years of emotions</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/next/2026/01/24/ai-sovereignty-requires-partnerships-not-isolation-says-world-economic-forums-ai-chief">2026-01-24 AI sovereignty requires partnerships, not isolation, says World Economic Forum’s AI chief</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/travel/2026/01/24/the-major-new-travel-hubs-that-are-going-to-change-the-way-we-fly">2026-01-24 The major new travel hubs that are going to change the way we fly</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/my-europe/2026/01/24/how-europeans-closed-ranks-to-defend-greenland-against-trump">2026-01-24 How Europeans closed ranks to defend Greenland against Trump</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/green/2026/01/24/brazilian-mosquitoes-grow-increasing-appetite-for-human-blood-as-atlantic-forest-shrinks">2026-01-24 Brazilian mosquitoes grow increasing appetite for human blood as Atlantic forest shrinks</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/business/2026/01/24/where-is-europes-cheapest-pizza-prices-rise-as-diners-feel-the-squeeze">2026-01-24 Where is Europe’s cheapest pizza? Prices rise as diners feel the squeeze</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/video/2026/01/24/latest-news-bulletin-january-24th-2026-morning">2026-01-24 Latest news bulletin | January 24th, 2026 – Morning</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/2026/01/23/russia-releases-video-showing-air-forces-patrolling-the-baltic-sea-in-planned-flights">2026-01-23 Russia releases video showing Air Forces patrolling the Baltic Sea in 'planned flights'</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/my-europe/2026/01/23/electrifying-everything-is-key-to-europes-future-iea-chief-tells-euronews-at-davos">2026-01-23 'Electrifying everything' is key to Europe's future, IEA chief tells Euronews at Davos</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/2026/01/23/italy-germany-summit-meloni-and-merz-push-for-an-autonomous-competitive-europe">2026-01-23 Italy-Germany summit: Meloni and Merz push for an autonomous, competitive Europe</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/2026/01/23/greenland-very-happy-with-the-eu-for-support-in-face-of-trump-takeover-threats-politician-">2026-01-23 Greenland 'very happy with the EU' for support in face of Trump takeover threats, politician says</a></td></tr> +<tr><td>→</td><td><a href="http://www.euronews.com/video/2026/01/23/starmer-calls-trumps-remarks-about-nato-troops-in-afghanistan-insulting-urges-apology">2026-01-23 Starmer calls Trump's remarks about Nato troops in Afghanistan 'insulting', urges apology</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/news-euronews/title b/feeds/news-euronews/title new file mode 100644 index 0000000..4f273db --- /dev/null +++ b/feeds/news-euronews/title @@ -0,0 +1 @@ +news - Euronews diff --git a/feeds/news-guardian-europe/feed b/feeds/news-guardian-europe/feed new file mode 100644 index 0000000..fa9096d --- /dev/null +++ b/feeds/news-guardian-europe/feed @@ -0,0 +1 @@ +https://www.theguardian.com/europe/rss diff --git a/feeds/news-guardian-europe/index.php b/feeds/news-guardian-europe/index.php new file mode 100644 index 0000000..67687c1 --- /dev/null +++ b/feeds/news-guardian-europe/index.php @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://www.theguardian.com/europe/rss">https://www.theguardian.com/europe/rss</a> (30 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://www.theguardian.com/sport/live/2026/jan/25/australian-open-2026-de-minaur-bublik-andreeva-svitolina-zverev-medvedev-live">2026-01-25 Australian Open 2026: De Minaur v Bublik, plus Zverev and Medvedev in action – live</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/football/live/2026/jan/25/arsenal-v-manchester-united-buildup-and-a-big-game-in-scotland-matchday-live">2026-01-25 Arsenal v Manchester United buildup, WSL action and Hearts v Celtic – matchday live</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/us-news/live/2026/jan/25/minneapolis-shooting-parents-of-alex-pretti-say-trump-officials-are-telling-sickening-lies">2026-01-25 Minneapolis shooting: parents of Alex Pretti say Trump officials are telling ‘sickening lies’</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/commentisfree/2026/jan/25/brooklyn-beckham-influencers-children-rebel">2026-01-25 Brooklyn Beckham and Prince Harry are the canaries in the coalmine. The children of Instagram will be next | Rhiannon Lucy Cosslett</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/lifeandstyle/2026/jan/25/we-cut-through-the-online-ocean-of-advice-the-rise-of-adult-sleep-coaching">2026-01-25 ‘We cut through the online ocean of advice’: the rise of adult sleep coaching</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/us-news/live/2026/jan/24/minneapolis-shooting-live">2026-01-25 Witnesses contradict Trump officials’ account of killing – as it happened</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/money/2026/jan/25/fitness-fraud-gym-scams-money-fake-deals-apps">2026-01-25 Fitness fraud: gym goers warned over fake deals on memberships and personal trainers</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/thefilter/2026/jan/25/best-womens-boots-uk">2026-01-25 The best women’s boots for winter: 24 favourites, from knee-high to ankle</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/travel/2026/jan/25/10-best-healthy-retreats-europe-nature-creative-workshop-yoga-meditation">2026-01-25 10 of the best retreats in Europe to soothe mind, body and soul</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/sport/2026/jan/25/jay-vine-recovers-from-kangaroo-crash-to-win-tour-down-under-for-second-time">2026-01-25 Jay Vine recovers from kangaroo crash to win Tour Down Under for second time</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/sport/2026/jan/25/carlos-alcaraz-beats-tommy-paul-match-report-australian-open">2026-01-25 Carlos Alcaraz overcomes spirited Tommy Paul to reach Australian Open quarter-finals without dropping a set</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/australia-news/2026/jan/25/dingoes-on-australias-kgari-island-to-be-euthanised-after-tragic-death-of-canadian-tourist-piper-james">2026-01-25 Dingoes on Australia’s K’gari island to be euthanised after tragic death of Canadian tourist Piper James</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/news/ng-interactive/2026/jan/25/china-europe-canada-charm-offensive-trump">2026-01-25 Trump’s wrecking ball pushes US allies closer to China</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/politics/2026/jan/25/ai-generated-british-schoolgirl-becomes-far-right-social-media-meme">2026-01-25 AI-generated British schoolgirl becomes far-right social media meme</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/commentisfree/2026/jan/25/council-homes-somerset-uk-glorious-proof-labour">2026-01-25 In Somerset, I found glorious proof that the UK can build great council houses. So what is holding us back? | John Harris</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/lifeandstyle/2026/jan/25/confessions-of-a-telephone-psychic">2026-01-25 ‘I was probably just as lost as my callers’: my six months as a telephone psychic</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/food/2026/jan/25/tin-roof-cafe-maldon-essex-grace-dent-restaurant-review">2026-01-25 Tin Roof Cafe, Maldon, Essex: ‘Come for topsoil, stay for the shortbread’ – review</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/us-news/2026/jan/25/congressman-maxwell-alejandro-frost-attack-police">2026-01-25 Democratic congressman punched in racist attack at Sundance film festival</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/us-news/2026/jan/24/alex-pretti-killing-witness-testimony">2026-01-25 Alex Pretti did not brandish gun, witnesses say in sworn testimony</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/us-news/2026/jan/25/trump-administration-news-updates-today">2026-01-25 Trump news at a glance: groundswell of anger at second fatal shooting by federal agents in weeks</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/film/2026/jan/24/josephine-channing-tatum-review">2026-01-25 Josephine review – Channing Tatum is a knockout in shattering drama of lost innocence</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/sport/2026/jan/24/alex-honnold-free-solo-taipei-101-netflix">2026-01-25 Alex Honnold successfully free solos Taipei 101 in live Netflix climb</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/australia-news/2026/jan/25/worst-heatwave-since-black-saturday-forecast-for-victoria-as-wa-faces-tropical-cyclone-luana">2026-01-25 Worst heatwave since Black Saturday forecast for Victoria as WA faces Tropical Cyclone Luana</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/us-news/2026/jan/24/protests-alex-pretti-killing-federal-agents-ice">2026-01-25 Large protests spread across US after Alex Pretti fatally shot by federal agents</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/us-news/2026/jan/24/minneapolis-shooting-ice">2026-01-25 Video contradicts Trump’s claim man killed in Minneapolis was a ‘gunman’</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/world/2026/jan/25/ukraine-war-briefing-kyiv-and-moscow-set-to-hold-more-face-to-face-talks-as-us-hails-big-step-forward">2026-01-25 Ukraine war briefing: Kyiv and Moscow set to hold more face-to-face talks as US hails ‘big step’ forward</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/world/2026/jan/25/myanmar-election-enters-final-stage-amid-airstrikes-and-exclusions">2026-01-25 Myanmar election enters final stage amid airstrikes and exclusions</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/uk-news/2026/jan/24/protesters-supporting-palestine-action-hunger-striker-arrested-outside-prison">2026-01-24 Protesters supporting Palestine Action hunger striker arrested outside prison</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/football/2026/jan/24/european-football-bayern-bundesliga-augsburg-real-madrid-bilbao-como">2026-01-24 European football: Augsburg fight back to end Bayern Munich’s unbeaten run</a></td></tr> +<tr><td>→</td><td><a href="https://www.theguardian.com/us-news/2026/jan/24/alex-pretti-minneapolis-what-we-know-so-far">2026-01-24 Alex Pretti killing in Minneapolis by federal agents: what we know so far</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/news-guardian-europe/title b/feeds/news-guardian-europe/title new file mode 100644 index 0000000..c7568c1 --- /dev/null +++ b/feeds/news-guardian-europe/title @@ -0,0 +1,2 @@ +news - The Guardian Europe + diff --git a/feeds/youtube-mental-outlaw/feed b/feeds/youtube-mental-outlaw/feed new file mode 100644 index 0000000..b067cb2 --- /dev/null +++ b/feeds/youtube-mental-outlaw/feed @@ -0,0 +1 @@ +https://www.youtube.com/feeds/videos.xml?playlist_id=UULF7YOGHUfC1Tb6E4pudI9STA diff --git a/feeds/youtube-mental-outlaw/index.php b/feeds/youtube-mental-outlaw/index.php new file mode 100644 index 0000000..81f6c90 --- /dev/null +++ b/feeds/youtube-mental-outlaw/index.php @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://www.youtube.com/feeds/videos.xml?playlist_id=UULF7YOGHUfC1Tb6E4pudI9STA">https://www.youtube.com/feeds/videos.xml?playlist_id=UULF7YOGHUfC1Tb6E4pudI9STA</a> (15 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://youtu.be/aaTvRDsdy0s">2026-01-24 Photoshop Finally Works on Linux</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/FyejToP1cjA">2026-01-17 TuxMate Makes Linux Desktop Setup a Breeze</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/BgeLeT4CoyY">2026-01-05 CachyOS Is Coming For Your Server</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/6GNqGZXHXpo">2025-12-25 When You Forget To Redact The Epstein Files</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/ETI5g-dTB7Y">2025-12-16 Desktop Linux Just Keeps Winning</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/0TLYLvGZUmA">2025-12-07 Jolla Phone - A Modern Linux Smart Phone</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/W6CVxpMBmOo">2025-12-04 Linux Shell Tricks For Managing Files</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/3MwJbRq3-rM">2025-11-27 The Best Desktop Linux For Windows Users</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/qEkiSBxLjUU">2025-11-22 Cloudflare Breaks the Internet (again)</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/m_Mr4bw8Pow">2025-11-18 Based Developer Makes AirPods Open Source</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/5Wv1FlUeeME">2025-11-14 Can Crowd Funded Tech Support Fix a Linux Sound Bug?</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/c8Phszco8TY">2025-11-08 China Is Forcing People To Watch Ads for Free Toilet Paper</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/A9h2npIn8RQ">2025-11-02 When the Surveillance State Falsely Accuses You</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/5NeRkZ1A4zY">2025-10-31 Rust Core Utils Are Breaking Ubuntu</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/D_z_w0m4A3E">2025-10-23 Desktop Linux is Under Attack</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/youtube-mental-outlaw/title b/feeds/youtube-mental-outlaw/title new file mode 100644 index 0000000..67ffe3a --- /dev/null +++ b/feeds/youtube-mental-outlaw/title @@ -0,0 +1 @@ +youtube - Mental Outlaw diff --git a/feeds/youtube-naomi-brockwell/feed b/feeds/youtube-naomi-brockwell/feed new file mode 100644 index 0000000..47951e2 --- /dev/null +++ b/feeds/youtube-naomi-brockwell/feed @@ -0,0 +1 @@ +https://www.youtube.com/feeds/videos.xml?channel_id=UCSuHzQ3GrHSzoBbwrIq3LLA diff --git a/feeds/youtube-naomi-brockwell/index.php b/feeds/youtube-naomi-brockwell/index.php new file mode 100644 index 0000000..33cd595 --- /dev/null +++ b/feeds/youtube-naomi-brockwell/index.php @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCSuHzQ3GrHSzoBbwrIq3LLA">https://www.youtube.com/feeds/videos.xml?channel_id=UCSuHzQ3GrHSzoBbwrIq3LLA</a> (15 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://youtu.be/70azaXOhjpI">2026-01-16 Using PGP With Proton</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/PgIxxWDRpbA">2026-01-01 3 Privacy Fixes Most People Never Make</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/Fshsk8MCAf4">2025-12-18 He Built a Privacy Tool. Now He’s Going to Prison.</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/rEuIPpdGaMY">2025-12-05 They Asked For My Name. I Said No.</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/qcd1ZCmFkiE">2025-11-14 Why You Need To Reboot More Often</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/IOa9tjoxsQ8">2025-10-24 The Creepy Cameras On Every Corner</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/Xr9l6xSrM28">2025-10-03 How Surveillance Turns You Into a Puppet of the Internet</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/uaexH72AOBo">2025-09-12 Why I Don't Use the Mac App Store (and What I Use Instead)</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/1DwOQwRn8_Y">2025-08-22 This is the moment the internet changed forever</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/dgd5_2pfEd0">2025-08-01 Setting up pseudonymous social media</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/1PeAfNBNARI">2025-07-11 She Said "Don’t Write That Down." The Doctor Did Anyway.</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/jeq2m-OM53A">2025-06-20 Your TV Is Spying On You and Taking Screenshots</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/gGva7O3czg0">2025-05-30 Think before you share</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/1g0RzOGxe40">2025-05-09 How To Make Your Mac PRIVATE</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/11P6DdI8HOo">2025-04-18 Browser Extensions are DANGEROUS</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/youtube-naomi-brockwell/title b/feeds/youtube-naomi-brockwell/title new file mode 100644 index 0000000..864dc4c --- /dev/null +++ b/feeds/youtube-naomi-brockwell/title @@ -0,0 +1 @@ +youtube - Naomi Brockwell diff --git a/feeds/youtube-veronica-explains/feed b/feeds/youtube-veronica-explains/feed new file mode 100644 index 0000000..fbf63c6 --- /dev/null +++ b/feeds/youtube-veronica-explains/feed @@ -0,0 +1 @@ +https://www.youtube.com/feeds/videos.xml?channel_id=UCMiyV_Ib77XLpzHPQH_q0qQ diff --git a/feeds/youtube-veronica-explains/index.php b/feeds/youtube-veronica-explains/index.php new file mode 100644 index 0000000..f12aef3 --- /dev/null +++ b/feeds/youtube-veronica-explains/index.php @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCMiyV_Ib77XLpzHPQH_q0qQ">https://www.youtube.com/feeds/videos.xml?channel_id=UCMiyV_Ib77XLpzHPQH_q0qQ</a> (15 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://youtu.be/Y4DKg4WZRx4">2026-01-05 Linux on a Mac (it's not going back)</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/JkwQJzCfogQ">2025-11-01 I stream nothing, and I am happy.</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/QKCIi-NxJEo">2025-10-01 Using rsync for backups, because it's not shiny and new</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/3Myvdu7hvtg">2025-09-06 Linux Mom prepping for the LUG like it's 2005</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/RSVaZwDFVrg">2025-09-05 So Action Retro, Taylor and Amy, and I just released an album</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/xu-XAyPJEeQ">2025-08-26 Livestream: installing a VM inside a VM inside a VM because chat told me to</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/T359mIW6wtg">2025-07-28 My Commodore 64 has an HDMI port now.</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/4_bYUVGgQQo">2025-06-17 The modern web sucks. My band's website doesn't.</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/PEFqdqRr18E">2025-05-26 I make all my videos using Linux. Here's how.</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/MSV5nImeYJg">2025-04-26 45Drives hasn't sent me any hardware and I still think they're neat</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/RmEtH5FQs28">2025-03-26 VimWiki: I write my notes in the terminal, like a monster</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/5cLO4zPQbCs">2025-03-03 How to install Debian Testing... and why you might not want to</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/qFV19WweP7M">2025-01-31 Clonezilla: disk cloning, but it's easier than dd</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/uL7KvRskeog">2025-01-05 Have you ever used the "column" command in Linux?</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/jKdy45ji4Vs">2024-12-24 Linux Q/A: desktop environments (Ask Veronica)</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/youtube-veronica-explains/title b/feeds/youtube-veronica-explains/title new file mode 100644 index 0000000..31018b1 --- /dev/null +++ b/feeds/youtube-veronica-explains/title @@ -0,0 +1 @@ +youtube - Veronica explains diff --git a/feeds/youtube-yousuckatprogramming/feed b/feeds/youtube-yousuckatprogramming/feed new file mode 100644 index 0000000..f78eeb0 --- /dev/null +++ b/feeds/youtube-yousuckatprogramming/feed @@ -0,0 +1 @@ +https://www.youtube.com/feeds/videos.xml?channel_id=UCMN0a7GHQnC6H74SmCGSmdw diff --git a/feeds/youtube-yousuckatprogramming/index.php b/feeds/youtube-yousuckatprogramming/index.php new file mode 100644 index 0000000..2199d75 --- /dev/null +++ b/feeds/youtube-yousuckatprogramming/index.php @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0" > +<meta name="description" content="Gopher without the crap" > +<link href="/.style.css" rel="stylesheet" type="text/css" > +<link rel="icon" href="/favicon.ico" type="image/x-icon" > +<link rel="icon" href="/favicon-64x64.png" type="image/png" > +<title>Spike.nagatha.fr</title> +</head> +<body> +<nav> +<div class="nav1"><a href="/">Spike.nagatha.fr</a></div> +<div class="nav2">/root/bin</div></nav> +<main> +<p>RSS feed : <a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCMN0a7GHQnC6H74SmCGSmdw">https://www.youtube.com/feeds/videos.xml?channel_id=UCMN0a7GHQnC6H74SmCGSmdw</a> (15 Items)</p> +<table><theader><th></th><th></th></theader><tbody> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/f4u0hgNMZe0">2026-01-24 the best way to get started with linux in general</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/PhxNZFRN-uw">2026-01-24 my full head of hair and unix beard make me unstoppable at linux</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/nN86z0pp018">2026-01-24 Time command quirks: when order matters</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/SkH0G8ktJyM">2026-01-22 Read Input Line by Line in Bash</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/Bmiazq9qsUk">2026-01-21 you can just add new syntax to bash</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/wvtw6hRttTk">2026-01-20 Bash Range Syntax: From One to Many</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/x43StZvKNzY">2026-01-18 Grep Flags: i and o Examples</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/Ou6ditHaXnA">2026-01-17 please don’t steal my software - it’s already free 😂</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/UL__XzgQGK4">2026-01-17 the bruce lee of bash scripting</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/PE8M4JOTjms">2026-01-17 i like to bring some positivity to the tech content world if possible!</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/JNcVX3SYtis">2026-01-16 Comparing Numbers in Bash: (( ... ))</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/-s_1amI6MOY">2026-01-16 we’ve had a fun past couple of days between the verizon outage and github going down.</a></td></tr> +<tr><td>→</td><td><a href="https://youtu.be/9fixlWcKWV8">2026-01-15 Building a Linux CPU Monitor in Bash From Scratch! small htop-like clone using the /proc filesystem</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/9gAWRYZQCbg">2026-01-15 Alternative Bash Syntax: What's Wild and What's Not</a></td></tr> +<tr><td>→</td><td><a href="https://www.youtube.com/shorts/eEKaJDy8odc">2026-01-14 Bash Basics: Understanding True and False Exit Codes</a></td></tr> +</tbody></table> +</main> +</body> +</html>
\ No newline at end of file diff --git a/feeds/youtube-yousuckatprogramming/title b/feeds/youtube-yousuckatprogramming/title new file mode 100644 index 0000000..fe4a485 --- /dev/null +++ b/feeds/youtube-yousuckatprogramming/title @@ -0,0 +1 @@ +youtube - You suck at programming diff --git a/files/bulletins/bulletin_032015_WEB.pdf b/files/bulletins/bulletin_032015_WEB.pdf Binary files differnew file mode 100755 index 0000000..9444584 --- /dev/null +++ b/files/bulletins/bulletin_032015_WEB.pdf diff --git a/files/bulletins/bulletin_052014_006_WEB.pdf b/files/bulletins/bulletin_052014_006_WEB.pdf Binary files differnew file mode 100755 index 0000000..dae8ece --- /dev/null +++ b/files/bulletins/bulletin_052014_006_WEB.pdf diff --git a/files/bulletins/bulletin_062015_web.pdf b/files/bulletins/bulletin_062015_web.pdf Binary files differnew file mode 100755 index 0000000..0fdf234 --- /dev/null +++ b/files/bulletins/bulletin_062015_web.pdf diff --git a/files/bulletins/bulletin_092014_01_WEB.pdf b/files/bulletins/bulletin_092014_01_WEB.pdf Binary files differnew file mode 100755 index 0000000..850e6bd --- /dev/null +++ b/files/bulletins/bulletin_092014_01_WEB.pdf diff --git a/files/bulletins/bulletin_122014_WEB.pdf b/files/bulletins/bulletin_122014_WEB.pdf Binary files differnew file mode 100755 index 0000000..c95e1a5 --- /dev/null +++ b/files/bulletins/bulletin_122014_WEB.pdf diff --git a/files/langourla.pdf b/files/langourla.pdf Binary files differnew file mode 100755 index 0000000..74c04c9 --- /dev/null +++ b/files/langourla.pdf diff --git a/gopher/de/index.html b/gopher/de/index.html new file mode 100755 index 0000000..d90caea --- /dev/null +++ b/gopher/de/index.html @@ -0,0 +1,124 @@ +<!DOCTYPE html> +<html lang="de"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher</title> +</head> +<body> +<a href="https://wittamore.fr/gopher"><img src="/gopher/images/gopher-dark.png" width="100px" alt="gopher"></a><br> +<h2>Was ist Gopher?</h2> +<p> +Das Gopher-Protokoll ist ein Kommunikationsprotokoll, das für die Verteilung, +Suche und den Abruf von Dokumenten in Internetprotokoll-Netzwerken entwickelt wurde. +Das Design des Gopher-Protokolls und der Benutzeroberfläche ist menügesteuert +und bietet eine Alternative zum World Wide Web für diejenigen, die dessen +überladen sind, und für Regionen der Welt, die aufgrund der Werbung und der +Überlastung des Netzes keine Verbindung herstellen können. +</p> +<h2>Warum Gopher?</h2> +<p> +Gopher ist eines der <a href="https://wittamore.fr/gopher/en/smolnet.html">Smolnet-Protokolle</a>, +das aufgrund der Unterdrückung des +World Wide Web durch Unternehmensinteressen, KI-Schlamassel und übermäßige +Komplexität, wo Ihre persönlichen Daten Geld einbringen und Sie das Produkt sind, +wieder an Interesse gewinnt. Gopher ist eine bewährte, einfache und unkomplizierte +Textlösung, die Text, Bilder, Dokumente, Videos, Audio, Streaming usw. +bereitstellen kann und mit fast jedem anderen Protokoll verknüpft werden kann, +solange Ihr Client oder Browser dies unterstützt. +</p> +<p> +Gopher hat kein Javascript, keine Cookies, kein CSS und keine Formatierungstags, +die Ihnen den Tag verderben könnten, sondern nur Inhalte und klar definierte Links. +Seine Einfachheit ist beabsichtigt, und deshalb ist es das ideale Sprungbrett zu +anderen Smolnet-Protokollen wie gemini:// oder spartan:// und einer Vielzahl +anderer. Nein, Sie sind nicht dazu verdammt, sich ewig aus dem Trog der +Unternehmensinteressen zu ernähren, es gibt da draußen ein ganz anderes Universum. +</p> +<h2>Wie funktioniert es?</h2> +<p> +Heute gibt es über 300 Gopher-Server, und ihre Zahl steigt weiter. Viele davon +sind Aggregatoren, d. h. sie ermöglichen es Menschen, ihre eigenen Phlogs +(Gopher-Blogs) zu betreiben, ohne Programmierkenntnisse zu haben oder eigene +Gopher-Server betreiben zu müssen. +</p><p> +Man findet dort Gedichte, Blogs, Romane, technische Schriften, Politik, Humor usw., +alles im Klartext und ohne Werbung, Pop-ups oder sonstige grafische oder kommerzielle +Störungen. Bei Gopher ist der Inhalt König. Außerdem senden Gopher-Server keine Cookies, +verwenden kein Javascript und erstellen keine Nutzerprofile. +</p><p> +Wenn Sie technisch versiert sind und lieber Ihren eigenen Gopher-Server hosten möchten, +ist das ganz einfach, genau wie das Gopher-Protokoll selbst. Ein Gopher-Server kann mit +wenigen Zeilen Bash-Skript geschrieben werden. Ausgefeiltere Server wie +<a href="https://github.com/gophernicus/gophernicus">Gophernicus</a> +bieten eine aktuelle und sichere Lösung und sind leicht verfügbar. +</p> +<h2>Wie verwende ich es?</h2> +<p> +Ein Gopher-Client oder Browser ist oft eine Frage des persönlichen Geschmacks, +ähnlich wie das World Wide Web früher, bevor ein bestimmtes globales Unternehmen +das Surfen im Internet zu seinem eigenen Vorteil übernommen hat. Gopher liefert reinen +Text, daher ist es Aufgabe des Clients, diesen ansprechend darzustellen. +</p><p> +<a href="https://gmi.skyjake.fi/lagrange/">Lagrange</a> +ist ein beliebter grafischer Browser, während +<a href="https://lynx.invisible-island.net/">Lynx</a> in einem Terminal eine +klassische Lösung darstellt. Vielleicht gefällt Ihnen auch ein moderner +Multiprotokoll-Client wie <a href="https://sr.ht/~bptato/chawan/">Chawan</a>, +mit dem Sie verschiedene Smolnet-Protokolle sowie +das Web ohne Unterbrechung durchsuchen können. Egal, ob Sie Windows, Linux, MacOS, +FreeBSD oder ein anderes Betriebssystem verwenden, es gibt eine Lösung für Sie. +</p><p> +Es gibt auch Web-Proxys, die es jemandem mit nur einem Webbrowser ermöglichen, +Gopher-Löcher zu besuchen. Sie sind jedoch nur eine vorübergehende Lösung und kein +Ersatz für einen dedizierten Gopher-Client. +</p> +<h2>Wie geht es nun weiter?</h2> +<p> +Probieren Sie es aus! Sie werden vielleicht überrascht sein, wie einfach die reinen +Textseiten sind, aber Sie werden schnell die Geschwindigkeit des Zugriffs aufgrund +der Übersichtlichkeit und die einfache Navigation zu schätzen wissen. Gopher ist nur +so gut wie sein Inhalt, es verlässt sich nicht auf ausgefallene Grafiken, um einen +Mangel an Substanz zu ersetzen. Zögern Sie also nicht, Ihre eigenen Texte hinzuzufügen! +Wir freuen uns darauf, sie zu lesen. +</p> +<h2>Screenshots</h2> +<ul> +<li><a href="/gopher/images/lagrange.png">Lagrange</a></li> +<li><a href="/gopher/images/chawan_1.png">Chawan</a></li> +<li><a href="/gopher/images/lynx_1.png">Lynx</a></li> +<li><a href="/gopher/images/gopher_elpher.png">Elpher</a></li> +<li><a href="/gopher/images/gopher_gophercle_android.jpg">Gophercle</a></li> +<li><a href="/gopher/images/gopher_little_gopher_client_mac.png">Little gopher client</a></li> +</ul> +<h2>Links</h2> +<h3>Client-Software</h3> +<ul> + <li><a href="https://gmi.skyjake.fi/lagrange/">Lagrange</a> ein GUI-Gopher- und Gemini-Client</li> + <li><a href="https://sr.ht/~julienxx/Castor/">Castor</a> ein GUI-Gopher- und Gemini-Client</li> + <li><a href="https://lynx.invisible-island.net/">Lynx</a> Ein klassischer Gopher- und Web-Client für das Terminal</li> + <li><a href="https://sr.ht/~bptato/chawan/">Chawan</a> ein Multiprotokoll-Client für das Terminal</li> + <li><a href="https://thelambdalab.xyz/elpher/">Elpher</a> Ein Gopher-Client für Emacs</li> +</ul> +<h3>Webbrowser-Erweiterungen</h3> +<ul> + <li><a href="https://addons.mozilla.org/en-US/firefox/addon/overbitewx/">OverbiteWX</a> ein Add-on für Mozilla Firefox</li> + <li><a href="https://chromewebstore.google.com/detail/burrow-gopherspace-explor/plhaaggiajlcjclagmjnjmaonhkdhhji">Burrow</a> ein Add-on für Google Chrome</li> +</ul> +<h3>Web-Proxys</h3> +<ul> + <li><a href="https://gopher.floodgap.com/gopher/">Floodgap Public Gopher Proxy</a></li> + <li><a href="https://gopherproxy.meulie.net/">Gopherproxy</a></li> +</ul> +<h3>Einige zufällige Gopher-Links</h3> +<ul> + <li><a href="gopher://gopher.floodgap.com:70/1/">Floodgap</a></li> + <li><a href="gopher://wittamore.fr">PAW</a></li> + <li><a href="gopher://lucio.albenga.es:70/1/">LFA</a></li> + <li><a href="gopher://gopherproject.org:70/1/">Gopher project</a></li> +</ul> + +<p> </p> +</body> +</html> diff --git a/gopher/de/smolnet.html b/gopher/de/smolnet.html new file mode 100755 index 0000000..eb25d11 --- /dev/null +++ b/gopher/de/smolnet.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher:Smolnet</title> +</head> +<body> +<a href="https://wittamore.fr/gopher"><img src="/gopher/images/gopher-dark.png" width="100px"></a><br> +<h1>Smolnet</h1> +<p>The SmolNet consists of content available through alternative protocols outside the web such as +<a href="https://geminiprotocol.net/">gemini://</a> +<a href="https://en.wikipedia.org/wiki/Gopher_(protocol)">gopher://</a> +<a href="https://github.com/gopher-protocol/gopher-plus/blob/main/gopherplus.md">Gopher+</a> +gophers:// +<a href="https://en.wikipedia.org/wiki/Finger_(protocol)">finger://</a> +<a href="https://portal.mozz.us/spartan/spartan.mozz.us/">spartan://</a> +<a href="https://textprotocol.org/">text://</a> +<a href="https://supertxt.net/00-intro.html">SuperText</a> +<a href="https://nightfall.city/nex/info/specification.txt">nex://</a> +<a href="https://github.com/zzo38/scorpion">scorpion://</a> +<a href="https://portal.mozz.us/gemini/zaibatsu.circumlunar.space/~solderpunk/gemlog/the-mercury-protocol.gmi">mercury://</a> +<a href="https://portal.mozz.us/gemini/transjovian.org/page/Titan">titan://</a> +<a href="https://github.com/dimkr/guppy-protocol">guppy://</a> +<a href="https://portal.mozz.us/gemini/scrollprotocol.us.to/">scroll://</a> +<a href="https://molerat.trinket.icu/">molerat://</a> +<a href="https://github.com/runvnc/tersenet/blob/master/README.md">terse://</a> +<a href="https://sourceforge.net/p/fsp/code/ci/master/tree/doc/PROTOCOL">fsp://</a>. +There is a <a href="https://dbohdan.com/archive/scorpion/zzo38computer.org/smallweb.txt">summary</a> +of the main SmolNet protocols. Related protocols include +<a href="https://en.wikipedia.org/wiki/Telnet">telnet://</a>, +<a href="https://wiki.archiveteam.org/index.php/Usenet">nntp:// or news://</a> and +<a href="https://en.wikipedia.org/wiki/DICT">dict://</a> +and more obscure things like <a href="https://datatracker.ietf.org/doc/html/rfc865">QOTD</a> +or <a href="https://managing.blue/2010/12/20/tcpmux-a-mostly-overlooked-tcp-service/">TCPMUX</a>. +</p> +<p> +source: <a href="https://wiki.archiveteam.org/index.php/SmolNet">wiki.archiveteam.org</a> +</p> + +</body> +</html> diff --git a/gopher/en/index.html b/gopher/en/index.html new file mode 100755 index 0000000..2c3b5e2 --- /dev/null +++ b/gopher/en/index.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher</title> +</head> +<body> +<a href="https://wittamore.fr/gopher"><img src="/gopher/images/gopher-dark.png" width="100px" alt="gopher"></a><br> +<h1>What is Gopher?</h1> +<p> +The Gopher protocol is a communication protocol designed for distributing, +searching, and retrieving documents in Internet Protocol networks. The +design of the Gopher protocol and user interface is menu-driven, and +presents an alternative to the World Wide Web for those who have become tired +of it, and where many parts of the world cannot even connect due to bloat. +</p> +<h2>Why Gopher?</h2> +<p> +Gopher is one of the <a href="/gopher/en/smolnet.html">smolnet</a> +protocols that is having a regain of interest due to the +smothering of the world wide web by corporate interests, AI slop, and overcomplexity, +where your personal data makes them money and you are the product. +Gopher is a tried and trusted, simple, plain text solution that can serve text, +images, documents, videos, audio, streaming, etc. and can link to almost +any other protocol as long as your client, or browser, supports it. +</p> +<p> +Gopher has no javascript, cookies, css, or formatting tags to ruin your day, only content and +clearly defined links. It's simplicity is by design, and because of this, +it is the ideal trampoline towards other smolnet protocols, +like gemini:// or spartan://, and a host of others. +No, you are not condemmed to be eternally feeding from the trough of corporate interests, +there's a whole other universe out there. +</p> + +<h2>How does it work?</h2> +<p> +Over 300 gopher servers exist today, and the number is increasing. Many of them are aggregators, +i.e. they allow people to run their own phlogs (gopher blogs) without any knowledge of programming +or need to run their own Gopher servers. +</p><p> +One can find poetry,blogs, novels, technical writings, politics, humour, etc. all in plain text and without +adverts, pop-ups, or any kind of graphical or commercial disturbance. Content is king on Gopher. +Also, gopher servers send no cookies, use javascript, or create user profiling. +</p> +<p> +If you are more technically inclined and would prefer to host your own gopher server, +it's quite simple, like the gopher protocol itself. A gopher server can be written +in a few lines of Bash script. More sophisticated servers like +<a href="https://github.com/gophernicus/gophernicus">gophernicus</a> +allow for an up to date and secure solution and are readily available. +</p> + +<h2>How do I use it?</h2> +<p> +A Gopher client, or browser is often a matter of personal taste, much like the world wide web of +old until a certain global corporation took over web browsing for their own benefit. +Gopher serves up plain text, so it's up to the client to render it nicely. +</p> +<p> +<a href="https://gmi.skyjake.fi/lagrange/">Lagrange</a> is a popular graphical browser, +while <a href="https://lynx.invisible-island.net/">Lynx</a> in a terminal is a classic solution. +You might also like a modern multiprotocol client such as <a href="https://sr.ht/~bptato/chawan/">Chawan</a> +that enables you to browse various smolnet protocols as well as the web without skipping a beat. +Whether you use Windows, Linux, MacOS, FreeBSD, or any other operating system, there's a solution for you. +</p> +<p> +There are also web proxies that allow someone with ony a web browser to visit gopher holes. However +they are a temporary solution, not a replacement for a dedicated Gopher client. +</p> + +<h2>Where do I go from here?</h2> +<p> +Try it out! You might be surprised at the simplicity of the text only pages, but you'll +quickly appreciate the speed of access due to lack of bloat, and the ease of browsing. +Gopher only as good as it's content, it doesn't rely on fancy graphics to replace a lack of substance, +so don't hesitate to add your own writings! We are looking forward to reading them. +</p> + +<h2>Screenshots</h2> +<ul> +<li><a href="/gopher/images/lagrange.png">Lagrange</a></li> +<li><a href="/gopher/images/chawan_1.png">Chawan</a></li> +<li><a href="/gopher/images/lynx_1.png">Lynx</a></li> +<li><a href="/gopher/images/gopher_elpher.png">Elpher</a></li> +<li><a href="/gopher/images/gopher_gophercle_android.jpg">Gophercle</a></li> +<li><a href="/gopher/images/gopher_little_gopher_client_mac.png">Little gopher client</a></li> +</ul> + +<h2>Links</h2> +<h3>Client software</h3> +<ul> + <li><a href="https://gmi.skyjake.fi/lagrange/">Lagrange</a> a GUI Gopher and Gemini client</li> + <li><a href="https://sr.ht/~julienxx/Castor/">Castor</a> a GUI Gopher and Gemini client</li> + <li><a href="https://lynx.invisible-island.net/">Lynx</a> a classic gopher and web client for the terminal</li> + <li><a href="https://sr.ht/~bptato/chawan/">Chawan</a> a multi-protocol client for the terminal</li> + <li><a href="https://thelambdalab.xyz/elpher/">Elpher</a> a gopher client for Emacs</li> +</ul> +<h3>Web browser extensions</h3> +<ul> + <li><a href="https://addons.mozilla.org/en-US/firefox/addon/overbitewx/">OverbiteWX</a> an add-on for Mozilla Firefox</li> + <li><a href="https://chromewebstore.google.com/detail/burrow-gopherspace-explor/plhaaggiajlcjclagmjnjmaonhkdhhji">Burrow</a> an add-on for Google Chrome</li> +</ul> +<h3>Web proxies</h3> +<ul> + <li><a href="https://gopher.floodgap.com/gopher/">Floodgap Public Gopher Proxy</a></li> + <li><a href="https://gopherproxy.meulie.net/">Gopherproxy</a></li> +</ul> +<h3>Some random Gopher links</h3> +<ul> + <li><a href="gopher://gopher.floodgap.com:70/1/">Floodgap</a></li> + <li><a href="gopher://wittamore.fr">PAW</a></li> + <li><a href="gopher://lucio.albenga.es:70/1/">LFA</a></li> + <li><a href="gopher://gopherproject.org:70/1/">Gopher project</a></li> +</ul> + +<p> </p> +</body> +</html> diff --git a/gopher/en/smolnet.html b/gopher/en/smolnet.html new file mode 100755 index 0000000..eb25d11 --- /dev/null +++ b/gopher/en/smolnet.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher:Smolnet</title> +</head> +<body> +<a href="https://wittamore.fr/gopher"><img src="/gopher/images/gopher-dark.png" width="100px"></a><br> +<h1>Smolnet</h1> +<p>The SmolNet consists of content available through alternative protocols outside the web such as +<a href="https://geminiprotocol.net/">gemini://</a> +<a href="https://en.wikipedia.org/wiki/Gopher_(protocol)">gopher://</a> +<a href="https://github.com/gopher-protocol/gopher-plus/blob/main/gopherplus.md">Gopher+</a> +gophers:// +<a href="https://en.wikipedia.org/wiki/Finger_(protocol)">finger://</a> +<a href="https://portal.mozz.us/spartan/spartan.mozz.us/">spartan://</a> +<a href="https://textprotocol.org/">text://</a> +<a href="https://supertxt.net/00-intro.html">SuperText</a> +<a href="https://nightfall.city/nex/info/specification.txt">nex://</a> +<a href="https://github.com/zzo38/scorpion">scorpion://</a> +<a href="https://portal.mozz.us/gemini/zaibatsu.circumlunar.space/~solderpunk/gemlog/the-mercury-protocol.gmi">mercury://</a> +<a href="https://portal.mozz.us/gemini/transjovian.org/page/Titan">titan://</a> +<a href="https://github.com/dimkr/guppy-protocol">guppy://</a> +<a href="https://portal.mozz.us/gemini/scrollprotocol.us.to/">scroll://</a> +<a href="https://molerat.trinket.icu/">molerat://</a> +<a href="https://github.com/runvnc/tersenet/blob/master/README.md">terse://</a> +<a href="https://sourceforge.net/p/fsp/code/ci/master/tree/doc/PROTOCOL">fsp://</a>. +There is a <a href="https://dbohdan.com/archive/scorpion/zzo38computer.org/smallweb.txt">summary</a> +of the main SmolNet protocols. Related protocols include +<a href="https://en.wikipedia.org/wiki/Telnet">telnet://</a>, +<a href="https://wiki.archiveteam.org/index.php/Usenet">nntp:// or news://</a> and +<a href="https://en.wikipedia.org/wiki/DICT">dict://</a> +and more obscure things like <a href="https://datatracker.ietf.org/doc/html/rfc865">QOTD</a> +or <a href="https://managing.blue/2010/12/20/tcpmux-a-mostly-overlooked-tcp-service/">TCPMUX</a>. +</p> +<p> +source: <a href="https://wiki.archiveteam.org/index.php/SmolNet">wiki.archiveteam.org</a> +</p> + +</body> +</html> diff --git a/gopher/es/index.html b/gopher/es/index.html new file mode 100755 index 0000000..6117de3 --- /dev/null +++ b/gopher/es/index.html @@ -0,0 +1,133 @@ +<!DOCTYPE html> +<html lang="es"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher</title> +</head> +<body> +<a href="https://wittamore.fr/gopher"><img src="/gopher/images/gopher-dark.png" width="100px" alt="gopher"></a><br> +<h1>¿Qué es Gopher?</h1> +<p> + Gopher es un protocolo de comunicación diseñado para compartir, buscar + y recuperar documentos en redes basadas en el protocolo IP (Internet + Protocol). El diseño del protocolo y la interfaz de usario de Gopher se + basan en menús y es una alternativa para aquellas personas que se han cansado + de la World Wide Web, o para los que viven en zonas del planeta con conexiones + que no les permiten acceder debido al peso excesivo de la web actual. +</p> +<h2>¿Por qué Gopher?</h2> +<p> + Gopher es uno de los protocolos de la <a href="./smolnet.html"><i>smolnet</i></a> + que está resurgiendo debido a todos los abusos en la web por parte de intereses corporativos, + contenido basura generado por IA y un exceso de complejidad, en el que se + busca convertirte en un producto y ganar dinero con tus datos personales. + Gopher es una solución fiable, probada, simple y en texto plano, con la que + se pueden compartir textos, imágenes, documentos, vídeos, audio, streaming, etc., + y que además puede enlazar con diferentes protocolos (siempre y cuando tu cliente + o navegador lo soporte). +</p> +<p> + Gopher no tiene ni JavaScript, ni cookies, ni CSS, ni etiquetas de formato + que te compliquen la vida; solo contenido y enlaces claramente definidos. Su + simplicidad es deliberada y por eso es el trampolín ideal hacia otros protocolos + de la <i>smolnet</i>, como gemini:// o spartan:// (entre otros). No, no estás condenado + de por vida a pasar por el aro de las grandes corporaciones. Hay todo un universo + ahí fuera. +</p> + +<h2>¿Cómo funciona?</h2> +<p> + Hoy en día existen más de 300 servidores Gopher en línea, y la cifra va en + aumento. Muchos de ellos son espacios compartidos, es decir, permiten a + la gente gestionar sus propios phlogs (blogs en Gopher) sin la necesidad de + conocimientos técnicos. +</p> +<p> + Puedes encontrar poesía, blogs, novelas, textos técnicos, política, humor, + y un largo etc., todo ello en texto plano y sin anuncios, sin ventanas + emergentes y sin ningún tipo de interrupción gráfica o comercial. En Gopher, + el contenido es el rey. Además los servidores Gopher no envían cookies, + no usan JavaScript y no crean perfiles de usuario. +</p> +<p> + Si tienes intereses técnicos y prefieres alojar tu propio servidor Gopher + es bastante sencillo, igual que el propio protocolo. Un servidor Gopher puede + escribirse en unas pocas líneas de Bash. Servidores más sofisticados como + <a href="https://github.com/gophernicus/gophernicus">Gophernicus</a> están al + alcance de cualquiera y ofrecen una solución actual y segura. +</p> + + +<h2>¿Cómo lo uso?</h2> +<p> + Elegir un cliente o navegador Gopher suele ser una cuestión de gustos personales, + igual que ocurría en la web primigenia hasta que cierta corporación global + se adueñó de la navegación web para su propio beneficio. Gopher devuelve texto plano + así que es el programa cliente quien decide cómo presentarlo. +</p> +<p> + <a href="https://gmi.skyjake.fi/lagrange/">Lagrange</a> es un navegador gráfico + muy popular, mientras que <a href="https://lynx.invisible-island.net/">Lynx</a> + es una solución clásica para la terminal. También puedes preferir un cliente + multiprotocolo moderno como <a href="https://sr.ht/~bptato/chawan/">Chawan</a>, que + te permite navegar por varios protocolos de la <i>smolnet</i> y por la web de forma + fluida. Tanto si usas Windows como Linux, macOS, FreeBSD u otro sistema operativo, + hay un programa cliente disponible. +</p> +<p> + También existen <i>proxies</i> via web que permiten a quien solamente tiene un navegador + web visitar <i>gopher holes</i> (el equivalente Gopher de una página web). Sin embargo estos deben + verse más bien como una solución temporal y no como un sustituto de un cliente Gopher dedicado. +</p> + +<h2>¿Y ahora qué?</h2> +<p> + ¡Pruébalo! Puede que al principio te sorprenda la sencillez de los contenidos + en texto plano, pero pronto apreciarás la velocidad de acceso gracias a la + ausencia de <i>bloatware</i> y la facilidad de navegación. Gopher es tan bueno + como su contenido, no hay gráficos llamativos para suplir la falta de sustancia, + así que ¡no dudes en añadir tus textos! Estamos deseando leerlos. +</p> + +<h2>Capturas de pantalla</h2> +<ul> +<li><a href="/images/lagrange.png">Lagrange</a></li> +<li><a href="/images/chawan_1.png">Chawan</a></li> +<li><a href="/images/lynx_1.png">Lynx</a></li> +<li><a href="/images/gopher_elpher.png">Elpher</a></li> +<li><a href="/images/gopher_gophercle_android.jpg">Gophercle</a></li> +<li><a href="/images/gopher_little_gopher_client_mac.png">Little gopher client</a></li> +</ul> + +<h2>Enlaces</h2> +<h3>Clientes / Navegadores</h3> +<ul> + <li><a href="https://gmi.skyjake.fi/lagrange/">Lagrange</a> cliente gráfico para Gopher y Gemini</li> + <li><a href="https://sr.ht/~julienxx/Castor/">Castor</a> otro cliente gráfico para Gopher y Gemini</li> + <li><a href="https://lynx.invisible-island.net/">Lynx</a> uno de los navegadores clásicos para la web y Gopher en terminal</li> + <li><a href="https://sr.ht/~bptato/chawan/">Chawan</a> un cliente en terminal multiprotocolo</li> + <li><a href="https://thelambdalab.xyz/elpher/">Elpher</a> cliente Gopher para Emacs</li> +</ul> +<h3>Extensiones de navegadores web</h3> +<ul> + <li><a href="https://addons.mozilla.org/en-US/firefox/addon/overbitewx/">OverbiteWX</a> extensíon para Mozilla Firefox</li> + <li><a href="https://chromewebstore.google.com/detail/burrow-gopherspace-explor/plhaaggiajlcjclagmjnjmaonhkdhhji">Burrow</a> extensión para Google Chrome</li> +</ul> +<h3>Proxies web</h3> +<ul> + <li><a href="https://gopher.floodgap.com/gopher/">Floodgap Public Gopher Proxy</a></li> + <li><a href="https://gopherproxy.meulie.net/">Gopherproxy</a></li> +</ul> +<h3>Enlaces varios</h3> +<ul> + <li><a href="gopher://gopher.floodgap.com:70/1/">Floodgap</a></li> + <li><a href="gopher://wittamore.fr">PAW</a></li> + <li><a href="gopher://lucio.albenga.es:70/1/">LFA</a></li> + <li><a href="gopher://gopherproject.org:70/1/">Gopher project</a></li> +</ul> + +<p> </p> +</body> +</html> diff --git a/gopher/es/smolnet.html b/gopher/es/smolnet.html new file mode 100755 index 0000000..7bf351b --- /dev/null +++ b/gopher/es/smolnet.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html lang="es"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher:Smolnet</title> +</head> +<body> +<a href="https://wittamore.fr/gopher"><img src="/gopher/images/gopher-dark.png" width="100px"></a><br> +<h1>Smolnet</h1> +<p>La <i>SmolNet</i> consiste en contenido disponible a través de protocolos alternativos fuera de la web, como +<a href="https://geminiprotocol.net/">gemini://</a> +<a href="https://en.wikipedia.org/wiki/Gopher_(protocol)">gopher://</a> +<a href="https://github.com/gopher-protocol/gopher-plus/blob/main/gopherplus.md">Gopher+</a> +gophers:// +<a href="https://en.wikipedia.org/wiki/Finger_(protocol)">finger://</a> +<a href="https://portal.mozz.us/spartan/spartan.mozz.us/">spartan://</a> +<a href="https://textprotocol.org/">text://</a> +<a href="https://supertxt.net/00-intro.html">SuperText</a> +<a href="https://nightfall.city/nex/info/specification.txt">nex://</a> +<a href="https://github.com/zzo38/scorpion">scorpion://</a> +<a href="https://portal.mozz.us/gemini/zaibatsu.circumlunar.space/~solderpunk/gemlog/the-mercury-protocol.gmi">mercury://</a> +<a href="https://portal.mozz.us/gemini/transjovian.org/page/Titan">titan://</a> +<a href="https://github.com/dimkr/guppy-protocol">guppy://</a> +<a href="https://portal.mozz.us/gemini/scrollprotocol.us.to/">scroll://</a> +<a href="https://molerat.trinket.icu/">molerat://</a> +<a href="https://github.com/runvnc/tersenet/blob/master/README.md">terse://</a> +<a href="https://sourceforge.net/p/fsp/code/ci/master/tree/doc/PROTOCOL">fsp://</a>. +Hay un <a href="https://dbohdan.com/archive/scorpion/zzo38computer.org/smallweb.txt">resumen</a> +de los principales protocolos de la <i>SmolNet</i>. Otros protocolos relacionados son +<a href="https://en.wikipedia.org/wiki/Telnet">telnet://</a>, +<a href="https://wiki.archiveteam.org/index.php/Usenet">nntp:// o news://</a>, +<a href="https://en.wikipedia.org/wiki/DICT">dict://</a> +y cosas más recónditas como <a href="https://datatracker.ietf.org/doc/html/rfc865">QOTD</a> +o <a href="https://managing.blue/2010/12/20/tcpmux-a-mostly-overlooked-tcp-service/">TCPMUX</a>. +</p> +<p> +fuente: <a href="https://wiki.archiveteam.org/index.php/SmolNet">wiki.archiveteam.org</a> +</p> + +</body> +</html> diff --git a/gopher/fr/index.html b/gopher/fr/index.html new file mode 100755 index 0000000..03f60cd --- /dev/null +++ b/gopher/fr/index.html @@ -0,0 +1,123 @@ +<!DOCTYPE html> +<html lang="fr"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher</title> +</head> +<body> +<a href="https://wittamore.fr/gopher"><img src="/gopher/images/gopher-dark.png" width="100px" alt="gopher"></a><br> +<h1>Qu'est-ce que Gopher ?</h1> +<p> +Le protocole Gopher est un protocole de communication conçu pour distribuer, +rechercher et récupérer des documents dans les réseaux IP (Internet Protocol). +La conception du protocole Gopher et de son interface utilisateur est basée +sur des menus. Il offre une alternative au World Wide Web pour ceux qui en +ont assez et pour les régions du monde qui ne peuvent même pas s'y connecter +en raison de sa surcharge. +</p> +<h2>Pourquoi Gopher ?</h2> +<p> +Gopher est l'un des protocoles <a href="/gopher/fr/smolnet.html">smolnet</a> qui suscite un regain d'intérêt en raison +de l'étouffement du World Wide Web par les intérêts des entreprises, +le bouillie de l'IA et la complexité excessive, où vos données personnelles +leur rapportent de l'argent car vous êtes le produit. Gopher est une solution +éprouvée, simple et fiable, qui permet de diffuser du texte, des images, des +documents, des vidéos, de l'audio, du streaming, etc. et qui peut se connecter +à presque tous les autres protocoles, à condition que votre client ou votre +navigateur le prenne en charge. +</p> +<p> +Gopher n'utilise pas de javascript, de cookies, de css ou de balises de formatage +qui pourraient gâcher votre journée, mais uniquement du contenu et des liens +clairement définis. Sa simplicité est voulue, et c'est pourquoi il constitue +le tremplin idéal vers d'autres protocoles smolnet, tels que gemini:// ou +spartan://, et bien d'autres encore. Non, vous n'êtes pas condamné à vous nourrir +éternellement à l'auge des intérêts des entreprises, il existe tout un autre univers. +</p> +<h2>Comment ça marche ?</h2> +<p> +Il existe aujourd'hui plus de 300 serveurs Gopher, et leur nombre ne cesse d'augmenter. +Beaucoup d'entre eux sont des agrégateurs, c'est-à-dire qu'ils permettent aux utilisateurs de gérer leurs propres phlogs +(blogs Gopher) sans avoir besoin de connaissances en programmation ni de gérer leurs propres serveurs Gopher. +</p> +<p> +On peut y trouver de la poésie, des blogs, des romans, des écrits techniques, des articles politiques, de l'humour, etc. +Le tout en texte brut, sans publicité, sans pop-up ni aucune autre forme de perturbation graphique ou +commerciale. Sur Gopher, le contenu est roi. De plus, les serveurs Gopher n'envoient +pas de cookies, n'utilisent pas de javascript et ne créent pas de profil utilisateur. +</p><p> +Si vous êtes plutôt doué en informatique et que vous préférez héberger votre propre serveur Gopher, +c'est assez simple, tout comme le protocole Gopher lui-même. Un serveur Gopher peut être écrit +en quelques lignes de script Bash. Des serveurs plus sophistiqués comme +<a href="https://github.com/gophernicus/gophernicus">gophernicus</a> +offrent une solution sécurisée et à jour, et sont facilement disponibles. +</p> +<h2>Comment l'utiliser ?</h2> +<p> +Le choix d'un client ou d'un navigateur Gopher relève souvent des goûts personnels, +tout comme c'était le cas pour le World Wide Web avant qu'une certaine multinationale +ne s'empare de la navigation web à son propre profit. Gopher fournit du texte brut, +c'est donc au client de le rendre agréable à lire. +</p><p> +<a href="https://gmi.skyjake.fi/lagrange/">Lagrange</a> est un navigateur graphique populaire, +tandis que <a href="https://lynx.invisible-island.net/">Lynx</a> dans un terminal +est une solution classique. Vous aimerez peut-être aussi un client multiprotocole +moderne tel que <a href="https://sr.ht/~bptato/chawan/">Chawan</a>, +qui vous permet de naviguer sur divers protocoles smolnet +ainsi que sur le web sans perdre une miette. Que vous utilisiez Windows, Linux, +MacOS, FreeBSD ou tout autre système d'exploitation, il existe une solution pour vous. +</p><p> +Il existe également des proxys web qui permettent à toute personne disposant d'un +navigateur web de visiter des trous Gopher. Cependant, il s'agit d'une solution +temporaire qui ne remplace pas un client Gopher dédié. +</p> +<h2>Et maintenant, que faire ?</h2> +<p> +Essayez-le ! Vous serez peut-être surpris par la simplicité des pages contenant +uniquement du texte, mais vous apprécierez rapidement la rapidité d'accès due à +l'absence de fioritures et la facilité de navigation. Gopher n'est bon que par son +contenu, il ne compte pas sur des graphismes sophistiqués pour compenser un manque +de substance, alors n'hésitez pas à ajouter vos propres écrits ! +Nous sommes impatients de les lire. +</p> +<h2>Captures d'écran</h2> +<ul> +<li><a href="/gopher/images/lagrange.png">Lagrange</a></li> +<li><a href="/gopher/images/chawan_1.png">Chawan</a></li> +<li><a href="/gopher/images/lynx_1.png">Lynx</a></li> +<li><a href="/gopher/images/gopher_elpher.png">Elpher</a></li> +<li><a href="/gopher/images/gopher_gophercle_android.jpg">Gophercle</a></li> +<li><a href="/gopher/images/gopher_little_gopher_client_mac.png">Little gopher client</a></li> +</ul> + +<h2>Liens</h2> +<h3>Logiciels clients</h3> +<ul> + <li><a href="https://gmi.skyjake.fi/lagrange/">Lagrange</a> un client Gopher et Gemini avec interface graphique</li> + <li><a href="https://sr.ht/~julienxx/Castor/">Castor</a> un client Gopher et Gemini avec interface graphique</li> + <li><a href="https://lynx.invisible-island.net/">Lynx</a> un client Gopher et Web classique pour le terminal</li> + <li><a href="https://sr.ht/~bptato/chawan/">Chawan</a> un client multiprotocole pour le terminal</li> + <li><a href="https://thelambdalab.xyz/elpher/">Elpher</a> un client Gopher pour Emacs</li> +</ul> +<h3>Extensions de navigateur</h3> +<ul> + <li><a href="https://addons.mozilla.org/en-US/firefox/addon/overbitewx/">OverbiteWX</a> un module complémentaire pour Mozilla Firefox</li> + <li><a href="https://chromewebstore.google.com/detail/burrow-gopherspace-explor/plhaaggiajlcjclagmjnjmaonhkdhhji">Burrow</a> un module complémentaire pour Google Chrome</li> +</ul> +<h3>Proxys Web</h3> +<ul> + <li><a href="https://gopher.floodgap.com/gopher/">Floodgap Public Gopher Proxy</a></li> + <li><a href="https://gopherproxy.meulie.net/">Gopherproxy</a></li> +</ul> +<h3>Quelques liens Gopher aléatoires</h3> +<ul> + <li><a href="gopher://gopher.floodgap.com:70/1/">Floodgap</a></li> + <li><a href="gopher://wittamore.fr">PAW</a></li> + <li><a href="gopher://lucio.albenga.es:70/1/">LFA</a></li> + <li><a href="gopher://gopherproject.org:70/1/">Projet Gopher</a></li> +</ul> +<p> </p> +</body> +</html> diff --git a/gopher/fr/smolnet.html b/gopher/fr/smolnet.html new file mode 100755 index 0000000..f7fd792 --- /dev/null +++ b/gopher/fr/smolnet.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher:Smolnet</title> +</head> +<body> +<a href="https://wittamore.fr/gopher"><img src="/gopher/images/gopher-dark.png" width="100px"></a><br> +<h1>Smolnet</h1> +<p>Le SmolNet comprend des contenus accessibles via des protocoles alternatifs en dehors du Web, tels que +<a href="https://geminiprotocol.net/">gemini://</a> +<a href="https://en.wikipedia.org/wiki/Gopher_(protocol)">gopher://</a> +<a href="https://github.com/gopher-protocol/gopher-plus/blob/main/gopherplus.md">Gopher+</a> +gophers:// +<a href="https://en.wikipedia.org/wiki/Finger_(protocol)">finger://</a> +<a href="https://portal.mozz.us/spartan/spartan.mozz.us/">spartan://</a> +<a href="https://textprotocol.org/">text://</a> +<a href="https://supertxt.net/00-intro.html">SuperText</a> +<a href="https://nightfall.city/nex/info/specification.txt">nex://</a> +<a href="https://github.com/zzo38/scorpion">scorpion://</a> +<a href="https://portal.mozz.us/gemini/zaibatsu.circumlunar.space/~solderpunk/gemlog/the-mercury-protocol.gmi">mercury://</a> +<a href="https://portal.mozz.us/gemini/transjovian.org/page/Titan">titan://</a> +<a href="https://github.com/dimkr/guppy-protocol">guppy://</a> +<a href="https://portal.mozz.us/gemini/scrollprotocol.us.to/">scroll://</a> +<a href="https://molerat.trinket.icu/">molerat://</a> +<a href="https://github.com/runvnc/tersenet/blob/master/README.md">terse://</a> +<a href="https://sourceforge.net/p/fsp/code/ci/master/tree/doc/PROTOCOL">fsp://</a>. +Voici un <a href="https://dbohdan.com/archive/scorpion/zzo38computer.org/smallweb.txt">résumé</a> +des principaux protocoles. Les protocoles associés comprennent +<a href="https://en.wikipedia.org/wiki/Telnet">telnet://</a>, +<a href="https://wiki.archiveteam.org/index.php/Usenet">nntp:// ou news://</a> et +<a href="https://en.wikipedia.org/wiki/DICT">dict://</a> +ainsi que d'autres protocoles moins connus tels que <a href="https://datatracker.ietf.org/doc/html/rfc865">QOTD</a> +ou <a href="https://managing.blue/2010/12/20/tcpmux-a-mostly-overlooked-tcp-service/">TCPMUX</a>. +</p> +<p> +source: <a href="https://wiki.archiveteam.org/index.php/SmolNet">wiki.archiveteam.org</a> +</p> + +</body> +</html> diff --git a/gopher/gdpr.html b/gopher/gdpr.html new file mode 100755 index 0000000..72b81a3 --- /dev/null +++ b/gopher/gdpr.html @@ -0,0 +1,106 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/gopher/style.css"> + <title>GDPR</title> + </head> + <body> + <h1>GDPR Notice</h1> +<h2>[English]</h2> +<p>gopher.fr is a personal site published in a non-professional capacity. +It does not use cookies, does not track visitors and does not store their e-mails or information +with the exception of temporary server logs for debugging purposes. Its content is free of rights. +</p> +<small> +<p>Details of the site host: +<br>OVH SAS, RCS Lille Métropole 424 761 419 00045 +<br>Head office: 2 rue Kellermann, 59100 Roubaix, France.</p> +</small> +<p>While I endeavour to keep the information up to date and correct, +I make no representations or warranties of any kind, +express or implied, about the completeness, accuracy, reliability, suitability +or availability of the information contained on the website for any purpose. +</p> +<p>All claims should be addressed to gopher@nagatha.fr</p> + +<h2>[Français]</h2> +<p> +gopher.fr est un site personel édité à titre non professionnel. +Il n'utilise pas de cookies, ne traque pas les visiteurs et ne stocke pas +leurs d'e-mails ni informations à l'exception des journaux temporaires du serveur à des fins de débogage. +Son contenu est libre des droits. +</p> + +<small> +<p>Coordonnées de l’hébergeur du site : +<br>OVH SAS, RCS Lille Métropole 424 761 419 00045 +<br>Siège social : 2 rue Kellermann, 59100 Roubaix, France.</p> +</small> + +<p> +Bien que je m'efforce de maintenir les informations à jour et correctes, +je ne fais aucune déclaration ni garantie de quelque nature que ce soit, +expresse ou implicite, concernant l'exhaustivité, l'exactitude, la fiabilité, la pertinence +ou la disponibilité des informations contenus sur le site web à quelque fin que ce soit. +</p> +<p>Toute réclamation doit être adressée à gopher@nagatha.fr</p> + +<h2>[Español]</h2> +<p> + gopher.fr es un sitio personal publicado a título no profesional. No utiliza cookies, no rastrea a los visitantes y no + almacena sus correos electrónicos ni su información, exceptuando logs temporales del servidor con fines de + depuración. Su contenido está libre de derechos. +</p> +<small> + <p> + Detalles del proveedor de alojamiento del sitio: + <br>OVH SAS, RCS Lille Métropole 424 761 419 00045 + <br>Sede central: 2 rue Kellermann, 59100 Roubaix, France. + </p> +</small> +<p> + Aunque me esfuerzo por mantener la información al día y correcta, no me hago responsable ni ofrezco + garantía alguna, expresa o implícita, sobre la integridad, exactitud, fiabilidad, idoneidad o disponibilidad de la + información contenida en este sitio web para cualquier fin. +</p> +<p>Todas las reclamaciones deben dirigirse a gopher@nagatha.fr</p> + +<h2>[Italiano]</h2> +<p> + gopher.fr è un sito personale pubblicato a titolo non professionale. Non utilizza cookie, non traccia gli utenti e + non memorizza le loro e-mail o informazioni, ad eccezione dei log temporanei del server a fini di debug. Il suo +</p> +<small> + <p>Dettagli dell’host del sito: + <br>OVH SAS, RCS Lille Métropole 424 761 419 00045 + <br>Sede centrale: 2 rue Kellermann, 59100 Roubaix, France. + </p> +</small> +<p> + Pur impegnandomi a mantenere le informazioni aggiornate e corrette, non mi assumo alcuna responsabilità né fornisco + garanzie di alcun tipo, espresse o implicite, riguardo alla completezza, accuratezza, affidabilità, idoneità o + disponibilità delle informazioni contenute in questo sito web per qualsiasi scopo. +</p> +<p>Tutti i reclami devono essere indirizzati a gopher@nagatha.fr</p> + +<h2>[Deutsch]</h2> +<p>gopher.fr ist eine private Website, die nicht zu gewerblichen Zwecken betrieben wird. +Sie verwendet keine Cookies, verfolgt keine Besucher und speichert keine E-Mail-Adressen oder Informationen +mit Ausnahme von temporären Server-Logs zu Debugging-Zwecken. Der Inhalt ist frei von Rechten. +</p> +<small> +<p>Angaben zum Host der Website: +<br>OVH SAS, RCS Lille Métropole 424 761 419 00045 +<br>Hauptsitz: 2 rue Kellermann, 59100 Roubaix, Frankreich.</p> +</small> +<p>Ich bemühe mich, die Informationen aktuell und korrekt zu halten, +übernehme jedoch keinerlei ausdrückliche oder stillschweigende Gewährleistung oder Zusicherung hinsichtlich der Vollständigkeit, Genauigkeit, Zuverlässigkeit, Eignung +oder Verfügbarkeit der auf der Website enthaltenen Informationen für irgendeinen Zweck. +</p> +<p>Alle Ansprüche sind an gopher@nagatha.fr zu richten. </p> + + <p> </p> + </body> +</html> diff --git a/gopher/gopherfr-translations.tar.gz b/gopher/gopherfr-translations.tar.gz Binary files differnew file mode 100755 index 0000000..bba8556 --- /dev/null +++ b/gopher/gopherfr-translations.tar.gz diff --git a/gopher/images/chawan_1.png b/gopher/images/chawan_1.png Binary files differnew file mode 100755 index 0000000..a76eb55 --- /dev/null +++ b/gopher/images/chawan_1.png diff --git a/gopher/images/chawan_2.png b/gopher/images/chawan_2.png Binary files differnew file mode 100755 index 0000000..22f0ba8 --- /dev/null +++ b/gopher/images/chawan_2.png diff --git a/gopher/images/gopher-dark.png b/gopher/images/gopher-dark.png Binary files differnew file mode 100755 index 0000000..e2d2e90 --- /dev/null +++ b/gopher/images/gopher-dark.png diff --git a/gopher/images/gopher.png b/gopher/images/gopher.png Binary files differnew file mode 100755 index 0000000..e5bbcf2 --- /dev/null +++ b/gopher/images/gopher.png diff --git a/gopher/images/gopher_IGICv1.12..png b/gopher/images/gopher_IGICv1.12..png Binary files differnew file mode 100755 index 0000000..8fda205 --- /dev/null +++ b/gopher/images/gopher_IGICv1.12..png diff --git a/gopher/images/gopher_elpher.png b/gopher/images/gopher_elpher.png Binary files differnew file mode 100755 index 0000000..c271e7d --- /dev/null +++ b/gopher/images/gopher_elpher.png diff --git a/gopher/images/gopher_gophercle_android.jpg b/gopher/images/gopher_gophercle_android.jpg Binary files differnew file mode 100755 index 0000000..6dd1f38 --- /dev/null +++ b/gopher/images/gopher_gophercle_android.jpg diff --git a/gopher/images/gopher_little_gopher_client_mac.png b/gopher/images/gopher_little_gopher_client_mac.png Binary files differnew file mode 100755 index 0000000..a790f07 --- /dev/null +++ b/gopher/images/gopher_little_gopher_client_mac.png diff --git a/gopher/images/gopherus.png b/gopher/images/gopherus.png Binary files differnew file mode 100755 index 0000000..4a7ea8e --- /dev/null +++ b/gopher/images/gopherus.png diff --git a/gopher/images/lagrange.png b/gopher/images/lagrange.png Binary files differnew file mode 100755 index 0000000..2dc5e0e --- /dev/null +++ b/gopher/images/lagrange.png diff --git a/gopher/images/lynx_1.png b/gopher/images/lynx_1.png Binary files differnew file mode 100755 index 0000000..12a7043 --- /dev/null +++ b/gopher/images/lynx_1.png diff --git a/gopher/index.html b/gopher/index.html new file mode 100755 index 0000000..af82e6b --- /dev/null +++ b/gopher/index.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html lang="fr"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher</title> +</head> +<body> +<h1>Gopher</h1> +<img src="/gopher/images/gopher-dark.png" width="400px" alt="gopher"><br> + +<p> </p> +<p> +<a href="/gopher/en/index.html">English</a> +<a href="/gopher/fr/index.html">Français</a> +<a href="/gopher/es/index.html">Español</a> +<a href="/gopher/it/index.html">Italiano</a> +<a href="/gopher/de/index.html">Deutsche</a> +</p> +<p> +<a href="/gopher/gdpr.html">GDPR</a> | <a href="https://smolweb.org/validator/"><w></a> +</p> +<p> </p> +</body> +</html> diff --git a/gopher/it/index.html b/gopher/it/index.html new file mode 100755 index 0000000..e7c5cc5 --- /dev/null +++ b/gopher/it/index.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<html lang="it"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" > +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher</title> +</head> +<body> +<a href="https://wittamore.fr/gopher"><img src="/gopher/images/gopher-dark.png" width="100px" alt="gopher"></a><br> +<h1>Che cos'è Gopher?</h1> +<p> + Gopher è un protocollo di comunicazione progettato per condividere, + cercare e recuperare documenti nelle reti basate su Internet Protocol. + La struttura del protocollo e dell'interfaccia utente di Gopher è basata + su menu e rappresenta un'alternativa al World Wide Web per chi si è stancato + di essa, o per chi vive in parti del mondo dove non è nemmeno possibile + collegarsi a causa del sovraccarico della web moderna. +</p> +<h2>Perché Gopher?</h2> +<p> + Gopher è uno dei protocolli della <a href="./smolnet.html"><i>smolnet</i></a> + che sta vivendo una nuova ondata di interesse a causa dell'oppressione della web + da parte degli interessi corporativi, dalla spazzatura prodotta dall’IA e dell'eccessiva + complessità, dove i tuoi dati personali fanno guadagnare denaro ad altri e tu diventi il + prodotto. Gopher è una soluzione semplice, affidabile e collaudata, in testo semplice, che può + fornire testi, immagini, documenti, video, audio, streaming, ecc., e può collegarsi a quasi + qualsiasi altro protocollo, purché il tuo client o browser lo supporti. +</p> +<p> + Gopher non ha JavaScript, cookie, CSS o tag di formattazione che + possano rovinarti la giornata; solo contenuto e link chiaramente + definiti. La sua semplicità è intenzionale, e proprio per questo è il + trampolino ideale verso altri protocolli dello smolnet come gemini://, + spartan:// e molti altri. Non sei obbligato a vivere per sempre al + servizio degli interessi delle grandi corporazioni. Là fuori c’è un + mondo tutto da scoprire! +</p> + +<h2>Come funziona?</h2> +<p> + Oggi esistono oltre 300 server Gopher, e il numero è in aumento. Molti + di essi sono spazi condivisi che permettono agli utenti di aprire i + propri phlog (blog Gopher) senza saper programmare né mettere in piedi + un server Gopher. +</p> +<p> + Si può trovare poesia, blog, romanzi, scritti tecnici, politica, + umorismo, ecc., tutto in testo semplice e senza pubblicità, pop-up o + qualsiasi tipo di invadenza grafica o commerciale. Su Gopher il + contenuto è ciò che conta davvero. Inoltre, i server Gopher non inviano + cookie, non usano JavaScript e non fanno profilazioni agli utenti. +</p> +<p> + Se ti interessano gli aspetti tecnici e preferisci ospitare il tuo server + Gopher, è abbastanza semplice, proprio come il protocollo stesso. Un + server Gopher può essere scritto in poche righe di Bash. Server più + sofisticati come <a href="https://github.com/gophernicus/gophernicus">Gophernicus</a> + sono alla portata di tutti e offrono una soluzione aggiornata e sicura. +</p> + + +<h2>Come lo uso?</h2> +<p> + Scegliere un client o browser Gopher è spesso una questione di gusto + personale, proprio come nel vecchio web dei primi tempi (prima che una + certa corporazione globale prendesse il controllo della navigazione web + per i propri interessi). Gopher fornisce testo semplice, quindi è il client + che decide come visualizzarlo. +</p> +<p> + <a href="https://gmi.skyjake.fi/lagrange/">Lagrange</a> è un client grafico + molto diffuso, mentre <a href="https://lynx.invisible-island.net/">Lynx</a> + è una soluzione classica nel terminale. Potrebbe anche piacerti un client + multiprotocollo moderno come <a href="https://sr.ht/~bptato/chawan/">Chawan</a>, + che ti consente di navigare tra vari protocolli dello smolnet e anche sul web + senza alcuna interruzione. Sia che tu usi Windows, Linux, macOS, FreeBSD o un + altro sistema operativo, esiste un client disponibile. +</p> +<p> + Esistono anche proxy web che permettono a chi dispone solo di un browser web di + visitare i gopher holes (l’equivalente, in Gopher, di una pagina web). Tuttavia, + dovrebbero essere visti come una soluzione provvisoria, non come un vero sostituto + di un client Gopher dedicato. +</p> + +<h2>E adesso?</h2> +<p> + Provalo! Le pagine esclusivamente testuali potrebbero sembrarti semplici all’inizio, + ma apprezzerai subito la velocità di accesso grazie all’assenza di bloatware e la + facilità di navigazione. Gopher è valido solo quanto lo è il contenuto, non si + basa su grafica appariscente per mascherare la mancanza di sostanza, + quindi non esitare ad aggiungere i tuoi scritti! Non vediamo l’ora di + leggerli. +</p> + +<h2>Schermate</h2> +<ul> +<li><a href="/images/lagrange.png">Lagrange</a></li> +<li><a href="/images/chawan_1.png">Chawan</a></li> +<li><a href="/images/lynx_1.png">Lynx</a></li> +<li><a href="/images/gopher_elpher.png">Elpher</a></li> +<li><a href="/images/gopher_gophercle_android.jpg">Gophercle</a></li> +<li><a href="/images/gopher_little_gopher_client_mac.png">Little gopher client</a></li> +</ul> + +<h2>Link</h2> +<h3>Client</h3> +<ul> + <li><a href="https://gmi.skyjake.fi/lagrange/">Lagrange</a> client grafico per Gopher e Gemini</li> + <li><a href="https://sr.ht/~julienxx/Castor/">Castor</a> un altro client grafico per Gopher e Gemini</li> + <li><a href="https://lynx.invisible-island.net/">Lynx</a> client classico in terminale per la web e Gopher</li> + <li><a href="https://sr.ht/~bptato/chawan/">Chawan</a> client multiprotocollo in terminale</li> + <li><a href="https://thelambdalab.xyz/elpher/">Elpher</a> client Gopher per Emacs</li> +</ul> +<h3>Estensioni per browser</h3> +<ul> + <li><a href="https://addons.mozilla.org/en-US/firefox/addon/overbitewx/">OverbiteWX</a> estensione per Mozilla Firefox</li> + <li><a href="https://chromewebstore.google.com/detail/burrow-gopherspace-explor/plhaaggiajlcjclagmjnjmaonhkdhhji">Burrow</a> estensione per Google Chrome</li> +</ul> +<h3>Proxy web</h3> +<ul> + <li><a href="https://gopher.floodgap.com/gopher/">Floodgap Public Gopher Proxy</a></li> + <li><a href="https://gopherproxy.meulie.net/">Gopherproxy</a></li> +</ul> +<h3>Selezione di link Gopher vari</h3> +<ul> + <li><a href="gopher://gopher.floodgap.com:70/1/">Floodgap</a></li> + <li><a href="gopher://wittamore.fr">PAW</a></li> + <li><a href="gopher://lucio.albenga.es:70/1/">LFA</a></li> + <li><a href="gopher://gopherproject.org:70/1/">Gopher project</a></li> +</ul> +<p> </p> +</body> +</html> diff --git a/gopher/it/smolnet.html b/gopher/it/smolnet.html new file mode 100755 index 0000000..a79433f --- /dev/null +++ b/gopher/it/smolnet.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html lang="it"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" href="/gopher/style.css"> +<title>Gopher:Smolnet</title> +</head> +<body> +<a href="https://wittamore.fr/gopher"><img src="/gopher/images/gopher-dark.png" width="100px"></a><br> +<h1>Smolnet</h1> +<p>La <i>SmolNet</i> consiste in contenuti disponibili tramite protocolli alternativi al web, come +<a href="https://geminiprotocol.net/">gemini://</a> +<a href="https://en.wikipedia.org/wiki/Gopher_(protocol)">gopher://</a> +<a href="https://github.com/gopher-protocol/gopher-plus/blob/main/gopherplus.md">Gopher+</a> +gophers:// +<a href="https://en.wikipedia.org/wiki/Finger_(protocol)">finger://</a> +<a href="https://portal.mozz.us/spartan/spartan.mozz.us/">spartan://</a> +<a href="https://textprotocol.org/">text://</a> +<a href="https://supertxt.net/00-intro.html">SuperText</a> +<a href="https://nightfall.city/nex/info/specification.txt">nex://</a> +<a href="https://github.com/zzo38/scorpion">scorpion://</a> +<a href="https://portal.mozz.us/gemini/zaibatsu.circumlunar.space/~solderpunk/gemlog/the-mercury-protocol.gmi">mercury://</a> +<a href="https://portal.mozz.us/gemini/transjovian.org/page/Titan">titan://</a> +<a href="https://github.com/dimkr/guppy-protocol">guppy://</a> +<a href="https://portal.mozz.us/gemini/scrollprotocol.us.to/">scroll://</a> +<a href="https://molerat.trinket.icu/">molerat://</a> +<a href="https://github.com/runvnc/tersenet/blob/master/README.md">terse://</a> +<a href="https://sourceforge.net/p/fsp/code/ci/master/tree/doc/PROTOCOL">fsp://</a>. +C’è un <a href="https://dbohdan.com/archive/scorpion/zzo38computer.org/smallweb.txt">elenco</a> +dei principali protocolli della SmolNet. Altri protocolli correlati sono +<a href="https://en.wikipedia.org/wiki/Telnet">telnet://</a>, +<a href="https://wiki.archiveteam.org/index.php/Usenet">nntp:// o news://</a>, +<a href="https://en.wikipedia.org/wiki/DICT">dict://</a> +e altre cose meno conosciute, come <a href="https://datatracker.ietf.org/doc/html/rfc865">QOTD</a> +o <a href="https://managing.blue/2010/12/20/tcpmux-a-mostly-overlooked-tcp-service/">TCPMUX</a>. +</p> +<p> +fonte: <a href="https://wiki.archiveteam.org/index.php/SmolNet">wiki.archiveteam.org</a> +</p> + +</body> +</html> diff --git a/gopher/style.css b/gopher/style.css new file mode 100755 index 0000000..86933c6 --- /dev/null +++ b/gopher/style.css @@ -0,0 +1,23 @@ +body,html { + background-color:#046786; + color: #E0E0E0; + font-family:monospace; + font-size:1.1em; + height:100%; +} +body { + margin: 0 auto; + max-width: 67ch; + min-height:100%; +} +h1,h2,h3,h4 { + color:#f1fa19; +} +h1 { + border-bottom: 1px solid #ffffff; + padding-bottom: 4px; +} +p { + text-align: justify; +} +a { color: #ffffff;} diff --git a/images/1.png b/images/1.png Binary files differnew file mode 100755 index 0000000..3f6f07c --- /dev/null +++ b/images/1.png diff --git a/images/Lenovo-ThinkPad-X220.JPEG b/images/Lenovo-ThinkPad-X220.JPEG Binary files differnew file mode 100755 index 0000000..79b7f9f --- /dev/null +++ b/images/Lenovo-ThinkPad-X220.JPEG diff --git a/images/Lenovo-ThinkPad-X220.webp b/images/Lenovo-ThinkPad-X220.webp Binary files differnew file mode 100755 index 0000000..1e23c3d --- /dev/null +++ b/images/Lenovo-ThinkPad-X220.webp diff --git a/images/ac-7620-spot.jpg b/images/ac-7620-spot.jpg Binary files differnew file mode 100755 index 0000000..5ccbbdd --- /dev/null +++ b/images/ac-7620-spot.jpg diff --git a/images/alacritty.JPEG b/images/alacritty.JPEG Binary files differnew file mode 100755 index 0000000..1323d38 --- /dev/null +++ b/images/alacritty.JPEG diff --git a/images/alacritty.webp b/images/alacritty.webp Binary files differnew file mode 100755 index 0000000..0856f60 --- /dev/null +++ b/images/alacritty.webp diff --git a/images/applixware-labelmaker_0.jpg b/images/applixware-labelmaker_0.jpg Binary files differnew file mode 100755 index 0000000..fb3a7d4 --- /dev/null +++ b/images/applixware-labelmaker_0.jpg diff --git a/images/applixware-labelmaker_1.jpg b/images/applixware-labelmaker_1.jpg Binary files differnew file mode 100755 index 0000000..405bee2 --- /dev/null +++ b/images/applixware-labelmaker_1.jpg diff --git a/images/arch-logo.JPEG b/images/arch-logo.JPEG Binary files differnew file mode 100755 index 0000000..43db338 --- /dev/null +++ b/images/arch-logo.JPEG diff --git a/images/arch-logo.webp b/images/arch-logo.webp Binary files differnew file mode 100755 index 0000000..004a481 --- /dev/null +++ b/images/arch-logo.webp diff --git a/images/balancoire.JPEG b/images/balancoire.JPEG Binary files differnew file mode 100755 index 0000000..a4f736a --- /dev/null +++ b/images/balancoire.JPEG diff --git a/images/balancoire.webp b/images/balancoire.webp Binary files differnew file mode 100755 index 0000000..fc396b2 --- /dev/null +++ b/images/balancoire.webp diff --git a/images/bandsaw-frame-pieces.JPEG b/images/bandsaw-frame-pieces.JPEG Binary files differnew file mode 100755 index 0000000..37c4d27 --- /dev/null +++ b/images/bandsaw-frame-pieces.JPEG diff --git a/images/bandsaw-frame-pieces.webp b/images/bandsaw-frame-pieces.webp Binary files differnew file mode 100755 index 0000000..d133e7c --- /dev/null +++ b/images/bandsaw-frame-pieces.webp diff --git a/images/bandsaw_1.JPEG b/images/bandsaw_1.JPEG Binary files differnew file mode 100755 index 0000000..529abd5 --- /dev/null +++ b/images/bandsaw_1.JPEG diff --git a/images/bandsaw_1.jpg b/images/bandsaw_1.jpg Binary files differnew file mode 100755 index 0000000..2d1ad7d --- /dev/null +++ b/images/bandsaw_1.jpg diff --git a/images/bandsaw_1.webp b/images/bandsaw_1.webp Binary files differnew file mode 100755 index 0000000..e88c831 --- /dev/null +++ b/images/bandsaw_1.webp diff --git a/images/blog/Lenovo-ThinkPad-X220.JPEG b/images/blog/Lenovo-ThinkPad-X220.JPEG Binary files differnew file mode 100755 index 0000000..98b4fb7 --- /dev/null +++ b/images/blog/Lenovo-ThinkPad-X220.JPEG diff --git a/images/blog/Lenovo-ThinkPad-X220.webp b/images/blog/Lenovo-ThinkPad-X220.webp Binary files differnew file mode 100755 index 0000000..2a597e5 --- /dev/null +++ b/images/blog/Lenovo-ThinkPad-X220.webp diff --git a/images/blog/Screenshot_2025-04-08_14-20-40.png b/images/blog/Screenshot_2025-04-08_14-20-40.png Binary files differnew file mode 100755 index 0000000..0043d02 --- /dev/null +++ b/images/blog/Screenshot_2025-04-08_14-20-40.png diff --git a/images/blog/alacritty.JPEG b/images/blog/alacritty.JPEG Binary files differnew file mode 100755 index 0000000..4828bba --- /dev/null +++ b/images/blog/alacritty.JPEG diff --git a/images/blog/alacritty.webp b/images/blog/alacritty.webp Binary files differnew file mode 100755 index 0000000..850920f --- /dev/null +++ b/images/blog/alacritty.webp diff --git a/images/blog/arch-logo.JPEG b/images/blog/arch-logo.JPEG Binary files differnew file mode 100755 index 0000000..342112a --- /dev/null +++ b/images/blog/arch-logo.JPEG diff --git a/images/blog/arch-logo.webp b/images/blog/arch-logo.webp Binary files differnew file mode 100755 index 0000000..a0592f8 --- /dev/null +++ b/images/blog/arch-logo.webp diff --git a/images/blog/balancoire.JPEG b/images/blog/balancoire.JPEG Binary files differnew file mode 100755 index 0000000..3f29d2f --- /dev/null +++ b/images/blog/balancoire.JPEG diff --git a/images/blog/balancoire.webp b/images/blog/balancoire.webp Binary files differnew file mode 100755 index 0000000..5e7f62a --- /dev/null +++ b/images/blog/balancoire.webp diff --git a/images/blog/bandsaw-frame-pieces.JPEG b/images/blog/bandsaw-frame-pieces.JPEG Binary files differnew file mode 100755 index 0000000..f329dbb --- /dev/null +++ b/images/blog/bandsaw-frame-pieces.JPEG diff --git a/images/blog/bandsaw-frame-pieces.webp b/images/blog/bandsaw-frame-pieces.webp Binary files differnew file mode 100755 index 0000000..367a040 --- /dev/null +++ b/images/blog/bandsaw-frame-pieces.webp diff --git a/images/blog/bandsaw_1.JPEG b/images/blog/bandsaw_1.JPEG Binary files differnew file mode 100755 index 0000000..07d2ae4 --- /dev/null +++ b/images/blog/bandsaw_1.JPEG diff --git a/images/blog/bandsaw_1.jpg b/images/blog/bandsaw_1.jpg Binary files differnew file mode 100755 index 0000000..ee882b4 --- /dev/null +++ b/images/blog/bandsaw_1.jpg diff --git a/images/blog/bandsaw_1.webp b/images/blog/bandsaw_1.webp Binary files differnew file mode 100755 index 0000000..a8c6411 --- /dev/null +++ b/images/blog/bandsaw_1.webp diff --git a/images/blog/bash-prompt-pywal.png b/images/blog/bash-prompt-pywal.png Binary files differnew file mode 100755 index 0000000..4b12018 --- /dev/null +++ b/images/blog/bash-prompt-pywal.png diff --git a/images/blog/battery.c.png b/images/blog/battery.c.png Binary files differnew file mode 100755 index 0000000..2d17652 --- /dev/null +++ b/images/blog/battery.c.png diff --git a/images/blog/bonneannee.gif b/images/blog/bonneannee.gif Binary files differnew file mode 100755 index 0000000..9eafaf0 --- /dev/null +++ b/images/blog/bonneannee.gif diff --git a/images/blog/box-chevaux.JPEG b/images/blog/box-chevaux.JPEG Binary files differnew file mode 100755 index 0000000..51b140b --- /dev/null +++ b/images/blog/box-chevaux.JPEG diff --git a/images/blog/box-chevaux.webp b/images/blog/box-chevaux.webp Binary files differnew file mode 100755 index 0000000..01d03b5 --- /dev/null +++ b/images/blog/box-chevaux.webp diff --git a/images/blog/carbide-tools.jpg b/images/blog/carbide-tools.jpg Binary files differnew file mode 100755 index 0000000..172a7ab --- /dev/null +++ b/images/blog/carbide-tools.jpg diff --git a/images/blog/cha-wittamore-1.png b/images/blog/cha-wittamore-1.png Binary files differnew file mode 100755 index 0000000..0207788 --- /dev/null +++ b/images/blog/cha-wittamore-1.png diff --git a/images/blog/chawan-ghostty.png b/images/blog/chawan-ghostty.png Binary files differnew file mode 100755 index 0000000..5b80d61 --- /dev/null +++ b/images/blog/chawan-ghostty.png diff --git a/images/blog/classic-keyboard.JPEG b/images/blog/classic-keyboard.JPEG Binary files differnew file mode 100755 index 0000000..6b313f2 --- /dev/null +++ b/images/blog/classic-keyboard.JPEG diff --git a/images/blog/classic-keyboard.webp b/images/blog/classic-keyboard.webp Binary files differnew file mode 100755 index 0000000..c206647 --- /dev/null +++ b/images/blog/classic-keyboard.webp diff --git a/images/blog/core-i5-cpu.JPEG b/images/blog/core-i5-cpu.JPEG Binary files differnew file mode 100755 index 0000000..4d46a64 --- /dev/null +++ b/images/blog/core-i5-cpu.JPEG diff --git a/images/blog/core-i5-cpu.png b/images/blog/core-i5-cpu.png Binary files differnew file mode 100755 index 0000000..ac3b076 --- /dev/null +++ b/images/blog/core-i5-cpu.png diff --git a/images/blog/core-i5-cpu.webp b/images/blog/core-i5-cpu.webp Binary files differnew file mode 100755 index 0000000..098946f --- /dev/null +++ b/images/blog/core-i5-cpu.webp diff --git a/images/blog/dane-success.png b/images/blog/dane-success.png Binary files differnew file mode 100755 index 0000000..d999593 --- /dev/null +++ b/images/blog/dane-success.png diff --git a/images/blog/dwm-btop.png b/images/blog/dwm-btop.png Binary files differnew file mode 100755 index 0000000..5f62865 --- /dev/null +++ b/images/blog/dwm-btop.png diff --git a/images/blog/dwm_bar.png b/images/blog/dwm_bar.png Binary files differnew file mode 100755 index 0000000..9f05063 --- /dev/null +++ b/images/blog/dwm_bar.png diff --git a/images/blog/dwmblocks-emoji.png b/images/blog/dwmblocks-emoji.png Binary files differnew file mode 100755 index 0000000..71bc0bf --- /dev/null +++ b/images/blog/dwmblocks-emoji.png diff --git a/images/blog/espion-dans-notre-poche.JPEG b/images/blog/espion-dans-notre-poche.JPEG Binary files differnew file mode 100755 index 0000000..3988f6e --- /dev/null +++ b/images/blog/espion-dans-notre-poche.JPEG diff --git a/images/blog/espion-dans-notre-poche.webp b/images/blog/espion-dans-notre-poche.webp Binary files differnew file mode 100755 index 0000000..aa156c7 --- /dev/null +++ b/images/blog/espion-dans-notre-poche.webp diff --git a/images/blog/etau.JPEG b/images/blog/etau.JPEG Binary files differnew file mode 100755 index 0000000..e816e64 --- /dev/null +++ b/images/blog/etau.JPEG diff --git a/images/blog/etau.webp b/images/blog/etau.webp Binary files differnew file mode 100755 index 0000000..b174490 --- /dev/null +++ b/images/blog/etau.webp diff --git a/images/blog/eu-flag.JPEG b/images/blog/eu-flag.JPEG Binary files differnew file mode 100755 index 0000000..9b22071 --- /dev/null +++ b/images/blog/eu-flag.JPEG diff --git a/images/blog/eu-flag.webp b/images/blog/eu-flag.webp Binary files differnew file mode 100755 index 0000000..27ac76e --- /dev/null +++ b/images/blog/eu-flag.webp diff --git a/images/blog/eza.png b/images/blog/eza.png Binary files differnew file mode 100755 index 0000000..96d7434 --- /dev/null +++ b/images/blog/eza.png diff --git a/images/blog/first-box.JPEG b/images/blog/first-box.JPEG Binary files differnew file mode 100755 index 0000000..3857478 --- /dev/null +++ b/images/blog/first-box.JPEG diff --git a/images/blog/first-box.webp b/images/blog/first-box.webp Binary files differnew file mode 100755 index 0000000..4395a6a --- /dev/null +++ b/images/blog/first-box.webp diff --git a/images/blog/fugitive-telemetry.JPEG b/images/blog/fugitive-telemetry.JPEG Binary files differnew file mode 100755 index 0000000..d62fd7a --- /dev/null +++ b/images/blog/fugitive-telemetry.JPEG diff --git a/images/blog/fugitive-telemetry.webp b/images/blog/fugitive-telemetry.webp Binary files differnew file mode 100755 index 0000000..6f6a619 --- /dev/null +++ b/images/blog/fugitive-telemetry.webp diff --git a/images/blog/ghostty.gif b/images/blog/ghostty.gif Binary files differnew file mode 100755 index 0000000..bcb4905 --- /dev/null +++ b/images/blog/ghostty.gif diff --git a/images/blog/grande-vase.JPEG b/images/blog/grande-vase.JPEG Binary files differnew file mode 100755 index 0000000..ee6901b --- /dev/null +++ b/images/blog/grande-vase.JPEG diff --git a/images/blog/grande-vase.webp b/images/blog/grande-vase.webp Binary files differnew file mode 100755 index 0000000..25122c3 --- /dev/null +++ b/images/blog/grande-vase.webp diff --git a/images/blog/gsimplecal.JPEG b/images/blog/gsimplecal.JPEG Binary files differnew file mode 100755 index 0000000..fb08053 --- /dev/null +++ b/images/blog/gsimplecal.JPEG diff --git a/images/blog/gsimplecal.webp b/images/blog/gsimplecal.webp Binary files differnew file mode 100755 index 0000000..a1eb487 --- /dev/null +++ b/images/blog/gsimplecal.webp diff --git a/images/blog/hail-mary-wier.JPEG b/images/blog/hail-mary-wier.JPEG Binary files differnew file mode 100755 index 0000000..880bb01 --- /dev/null +++ b/images/blog/hail-mary-wier.JPEG diff --git a/images/blog/hail-mary-wier.webp b/images/blog/hail-mary-wier.webp Binary files differnew file mode 100755 index 0000000..da6e5f0 --- /dev/null +++ b/images/blog/hail-mary-wier.webp diff --git a/images/blog/happy-new-year.gif b/images/blog/happy-new-year.gif Binary files differnew file mode 100755 index 0000000..6bc958e --- /dev/null +++ b/images/blog/happy-new-year.gif diff --git a/images/blog/i3-blocks.JPEG b/images/blog/i3-blocks.JPEG Binary files differnew file mode 100755 index 0000000..ae497bd --- /dev/null +++ b/images/blog/i3-blocks.JPEG diff --git a/images/blog/i3-blocks.webp b/images/blog/i3-blocks.webp Binary files differnew file mode 100755 index 0000000..2b3dd4d --- /dev/null +++ b/images/blog/i3-blocks.webp diff --git a/images/blog/kbca-jazz-radio.png b/images/blog/kbca-jazz-radio.png Binary files differnew file mode 100755 index 0000000..905e54e --- /dev/null +++ b/images/blog/kbca-jazz-radio.png diff --git a/images/blog/kobo-clara-colour.png b/images/blog/kobo-clara-colour.png Binary files differnew file mode 100755 index 0000000..703b7c3 --- /dev/null +++ b/images/blog/kobo-clara-colour.png diff --git a/images/blog/la-grande-illusion.JPEG b/images/blog/la-grande-illusion.JPEG Binary files differnew file mode 100755 index 0000000..9c1a793 --- /dev/null +++ b/images/blog/la-grande-illusion.JPEG diff --git a/images/blog/la-grande-illusion.webp b/images/blog/la-grande-illusion.webp Binary files differnew file mode 100755 index 0000000..06b921b --- /dev/null +++ b/images/blog/la-grande-illusion.webp diff --git a/images/blog/librefox-wittamore-1.png b/images/blog/librefox-wittamore-1.png Binary files differnew file mode 100755 index 0000000..8717d6e --- /dev/null +++ b/images/blog/librefox-wittamore-1.png diff --git a/images/blog/links-wittamore-1.png b/images/blog/links-wittamore-1.png Binary files differnew file mode 100755 index 0000000..79049f9 --- /dev/null +++ b/images/blog/links-wittamore-1.png diff --git a/images/blog/lnav.JPEG b/images/blog/lnav.JPEG Binary files differnew file mode 100755 index 0000000..e6c877e --- /dev/null +++ b/images/blog/lnav.JPEG diff --git a/images/blog/lnav.webp b/images/blog/lnav.webp Binary files differnew file mode 100755 index 0000000..17d2aac --- /dev/null +++ b/images/blog/lnav.webp diff --git a/images/blog/lower-blade-guide-1.JPEG b/images/blog/lower-blade-guide-1.JPEG Binary files differnew file mode 100755 index 0000000..e9cdf25 --- /dev/null +++ b/images/blog/lower-blade-guide-1.JPEG diff --git a/images/blog/lower-blade-guide-1.webp b/images/blog/lower-blade-guide-1.webp Binary files differnew file mode 100755 index 0000000..3bb7072 --- /dev/null +++ b/images/blog/lower-blade-guide-1.webp diff --git a/images/blog/maison-poupee.JPEG b/images/blog/maison-poupee.JPEG Binary files differnew file mode 100755 index 0000000..87d9ea9 --- /dev/null +++ b/images/blog/maison-poupee.JPEG diff --git a/images/blog/maison-poupee.webp b/images/blog/maison-poupee.webp Binary files differnew file mode 100755 index 0000000..5f20145 --- /dev/null +++ b/images/blog/maison-poupee.webp diff --git a/images/blog/mini-chariot.JPEG b/images/blog/mini-chariot.JPEG Binary files differnew file mode 100755 index 0000000..4ccae9f --- /dev/null +++ b/images/blog/mini-chariot.JPEG diff --git a/images/blog/mini-chariot.webp b/images/blog/mini-chariot.webp Binary files differnew file mode 100755 index 0000000..1a59726 --- /dev/null +++ b/images/blog/mini-chariot.webp diff --git a/images/blog/mygophersite.png b/images/blog/mygophersite.png Binary files differnew file mode 100755 index 0000000..3fed567 --- /dev/null +++ b/images/blog/mygophersite.png diff --git a/images/blog/neomutt-screenshot.png b/images/blog/neomutt-screenshot.png Binary files differnew file mode 100755 index 0000000..b9702cb --- /dev/null +++ b/images/blog/neomutt-screenshot.png diff --git a/images/blog/new-bench.JPEG b/images/blog/new-bench.JPEG Binary files differnew file mode 100755 index 0000000..4f6c2f9 --- /dev/null +++ b/images/blog/new-bench.JPEG diff --git a/images/blog/new-bench.webp b/images/blog/new-bench.webp Binary files differnew file mode 100755 index 0000000..e763620 --- /dev/null +++ b/images/blog/new-bench.webp diff --git a/images/blog/newsboat-cha-1.png b/images/blog/newsboat-cha-1.png Binary files differnew file mode 100755 index 0000000..439d4a5 --- /dev/null +++ b/images/blog/newsboat-cha-1.png diff --git a/images/blog/newsboat-rdrview-1.png b/images/blog/newsboat-rdrview-1.png Binary files differnew file mode 100755 index 0000000..851740a --- /dev/null +++ b/images/blog/newsboat-rdrview-1.png diff --git a/images/blog/oak-bowl-28cm.JPEG b/images/blog/oak-bowl-28cm.JPEG Binary files differnew file mode 100755 index 0000000..d6a5348 --- /dev/null +++ b/images/blog/oak-bowl-28cm.JPEG diff --git a/images/blog/oak-bowl-28cm.webp b/images/blog/oak-bowl-28cm.webp Binary files differnew file mode 100755 index 0000000..7a5beb3 --- /dev/null +++ b/images/blog/oak-bowl-28cm.webp diff --git a/images/blog/odysee.JPEG b/images/blog/odysee.JPEG Binary files differnew file mode 100755 index 0000000..0c9b59a --- /dev/null +++ b/images/blog/odysee.JPEG diff --git a/images/blog/odysee.webp b/images/blog/odysee.webp Binary files differnew file mode 100755 index 0000000..10a1eb1 --- /dev/null +++ b/images/blog/odysee.webp diff --git a/images/blog/pense-bete.JPEG b/images/blog/pense-bete.JPEG Binary files differnew file mode 100755 index 0000000..647005f --- /dev/null +++ b/images/blog/pense-bete.JPEG diff --git a/images/blog/pense-bete.webp b/images/blog/pense-bete.webp Binary files differnew file mode 100755 index 0000000..2e66add --- /dev/null +++ b/images/blog/pense-bete.webp diff --git a/images/blog/planche-à-découper-jacqueline.JPEG b/images/blog/planche-à-découper-jacqueline.JPEG Binary files differnew file mode 100755 index 0000000..5bc400d --- /dev/null +++ b/images/blog/planche-à-découper-jacqueline.JPEG diff --git a/images/blog/planche-à-découper-jacqueline.webp b/images/blog/planche-à-découper-jacqueline.webp Binary files differnew file mode 100755 index 0000000..62be8fe --- /dev/null +++ b/images/blog/planche-à-découper-jacqueline.webp diff --git a/images/blog/poster-oiseaux-des-jardins.png b/images/blog/poster-oiseaux-des-jardins.png Binary files differnew file mode 100755 index 0000000..76faebb --- /dev/null +++ b/images/blog/poster-oiseaux-des-jardins.png diff --git a/images/blog/powerline_bash.png b/images/blog/powerline_bash.png Binary files differnew file mode 100755 index 0000000..8f83f23 --- /dev/null +++ b/images/blog/powerline_bash.png diff --git a/images/blog/powermenu.png b/images/blog/powermenu.png Binary files differnew file mode 100755 index 0000000..f068ea2 --- /dev/null +++ b/images/blog/powermenu.png diff --git a/images/blog/pyradio-again.png b/images/blog/pyradio-again.png Binary files differnew file mode 100755 index 0000000..392f0ea --- /dev/null +++ b/images/blog/pyradio-again.png diff --git a/images/blog/pyradio.JPEG b/images/blog/pyradio.JPEG Binary files differnew file mode 100755 index 0000000..0010c93 --- /dev/null +++ b/images/blog/pyradio.JPEG diff --git a/images/blog/pyradio.webp b/images/blog/pyradio.webp Binary files differnew file mode 100755 index 0000000..bdafdf0 --- /dev/null +++ b/images/blog/pyradio.webp diff --git a/images/blog/pywal16-screenshot.png b/images/blog/pywal16-screenshot.png Binary files differnew file mode 100755 index 0000000..65a2a8b --- /dev/null +++ b/images/blog/pywal16-screenshot.png diff --git a/images/blog/resistance.jpg b/images/blog/resistance.jpg Binary files differnew file mode 100755 index 0000000..8eda020 --- /dev/null +++ b/images/blog/resistance.jpg diff --git a/images/blog/riddings-web-site.JPEG b/images/blog/riddings-web-site.JPEG Binary files differnew file mode 100755 index 0000000..43d3522 --- /dev/null +++ b/images/blog/riddings-web-site.JPEG diff --git a/images/blog/riddings-web-site.webp b/images/blog/riddings-web-site.webp Binary files differnew file mode 100755 index 0000000..5fce9be --- /dev/null +++ b/images/blog/riddings-web-site.webp diff --git a/images/blog/screw-jig.JPEG b/images/blog/screw-jig.JPEG Binary files differnew file mode 100755 index 0000000..fb98ed8 --- /dev/null +++ b/images/blog/screw-jig.JPEG diff --git a/images/blog/screw-jig.webp b/images/blog/screw-jig.webp Binary files differnew file mode 100755 index 0000000..05e47c6 --- /dev/null +++ b/images/blog/screw-jig.webp diff --git a/images/blog/semware.png b/images/blog/semware.png Binary files differnew file mode 100644 index 0000000..8d771af --- /dev/null +++ b/images/blog/semware.png diff --git a/images/blog/shadocks-pumping.gif b/images/blog/shadocks-pumping.gif Binary files differnew file mode 100755 index 0000000..32abdb6 --- /dev/null +++ b/images/blog/shadocks-pumping.gif diff --git a/images/blog/slim-theme.png b/images/blog/slim-theme.png Binary files differnew file mode 100755 index 0000000..6b732e9 --- /dev/null +++ b/images/blog/slim-theme.png diff --git a/images/blog/slstatus-french.png b/images/blog/slstatus-french.png Binary files differnew file mode 100755 index 0000000..a5ea132 --- /dev/null +++ b/images/blog/slstatus-french.png diff --git a/images/blog/slstatus.png b/images/blog/slstatus.png Binary files differnew file mode 100755 index 0000000..2af84f3 --- /dev/null +++ b/images/blog/slstatus.png diff --git a/images/blog/sous-plat-incrustation.JPEG b/images/blog/sous-plat-incrustation.JPEG Binary files differnew file mode 100755 index 0000000..a1936f5 --- /dev/null +++ b/images/blog/sous-plat-incrustation.JPEG diff --git a/images/blog/sous-plat-incrustation.webp b/images/blog/sous-plat-incrustation.webp Binary files differnew file mode 100755 index 0000000..2a38bd2 --- /dev/null +++ b/images/blog/sous-plat-incrustation.webp diff --git a/images/blog/starship-gruvbox-rainbow.png b/images/blog/starship-gruvbox-rainbow.png Binary files differnew file mode 100755 index 0000000..cd99e56 --- /dev/null +++ b/images/blog/starship-gruvbox-rainbow.png diff --git a/images/blog/stop-block.JPEG b/images/blog/stop-block.JPEG Binary files differnew file mode 100755 index 0000000..d4628f1 --- /dev/null +++ b/images/blog/stop-block.JPEG diff --git a/images/blog/stop-block.webp b/images/blog/stop-block.webp Binary files differnew file mode 100755 index 0000000..fd6f179 --- /dev/null +++ b/images/blog/stop-block.webp diff --git a/images/blog/support-roue-superior.JPEG b/images/blog/support-roue-superior.JPEG Binary files differnew file mode 100755 index 0000000..13db203 --- /dev/null +++ b/images/blog/support-roue-superior.JPEG diff --git a/images/blog/support-roue-superior.webp b/images/blog/support-roue-superior.webp Binary files differnew file mode 100755 index 0000000..f1d1180 --- /dev/null +++ b/images/blog/support-roue-superior.webp diff --git a/images/blog/support-trunnion.JPEG b/images/blog/support-trunnion.JPEG Binary files differnew file mode 100755 index 0000000..47ad421 --- /dev/null +++ b/images/blog/support-trunnion.JPEG diff --git a/images/blog/support-trunnion.webp b/images/blog/support-trunnion.webp Binary files differnew file mode 100755 index 0000000..3b00ca4 --- /dev/null +++ b/images/blog/support-trunnion.webp diff --git a/images/blog/table-support-trunions.JPEG b/images/blog/table-support-trunions.JPEG Binary files differnew file mode 100755 index 0000000..054024f --- /dev/null +++ b/images/blog/table-support-trunions.JPEG diff --git a/images/blog/table-support-trunions.webp b/images/blog/table-support-trunions.webp Binary files differnew file mode 100755 index 0000000..1c0b91c --- /dev/null +++ b/images/blog/table-support-trunions.webp diff --git a/images/blog/thinkpad-x220-mic-button.JPEG b/images/blog/thinkpad-x220-mic-button.JPEG Binary files differnew file mode 100755 index 0000000..8fee6e1 --- /dev/null +++ b/images/blog/thinkpad-x220-mic-button.JPEG diff --git a/images/blog/thinkpad-x220-mic-button.webp b/images/blog/thinkpad-x220-mic-button.webp Binary files differnew file mode 100755 index 0000000..d214907 --- /dev/null +++ b/images/blog/thinkpad-x220-mic-button.webp diff --git a/images/blog/thinkvantage.png b/images/blog/thinkvantage.png Binary files differnew file mode 100755 index 0000000..3eb194e --- /dev/null +++ b/images/blog/thinkvantage.png diff --git a/images/blog/tprg.JPEG b/images/blog/tprg.JPEG Binary files differnew file mode 100755 index 0000000..39e4928 --- /dev/null +++ b/images/blog/tprg.JPEG diff --git a/images/blog/tprg.webp b/images/blog/tprg.webp Binary files differnew file mode 100755 index 0000000..62988ab --- /dev/null +++ b/images/blog/tprg.webp diff --git a/images/blog/trunion-shim.JPEG b/images/blog/trunion-shim.JPEG Binary files differnew file mode 100755 index 0000000..000db96 --- /dev/null +++ b/images/blog/trunion-shim.JPEG diff --git a/images/blog/trunion-shim.webp b/images/blog/trunion-shim.webp Binary files differnew file mode 100755 index 0000000..07e0f60 --- /dev/null +++ b/images/blog/trunion-shim.webp diff --git a/images/blog/trunions-chevilles.JPEG b/images/blog/trunions-chevilles.JPEG Binary files differnew file mode 100755 index 0000000..f6df681 --- /dev/null +++ b/images/blog/trunions-chevilles.JPEG diff --git a/images/blog/trunions-chevilles.webp b/images/blog/trunions-chevilles.webp Binary files differnew file mode 100755 index 0000000..1ebd8a9 --- /dev/null +++ b/images/blog/trunions-chevilles.webp diff --git a/images/blog/upper-blade-guide-1.JPEG b/images/blog/upper-blade-guide-1.JPEG Binary files differnew file mode 100755 index 0000000..1f7ce98 --- /dev/null +++ b/images/blog/upper-blade-guide-1.JPEG diff --git a/images/blog/upper-blade-guide-1.webp b/images/blog/upper-blade-guide-1.webp Binary files differnew file mode 100755 index 0000000..408aa45 --- /dev/null +++ b/images/blog/upper-blade-guide-1.webp diff --git a/images/blog/w3m-wittamore-1.png b/images/blog/w3m-wittamore-1.png Binary files differnew file mode 100755 index 0000000..f710f82 --- /dev/null +++ b/images/blog/w3m-wittamore-1.png diff --git a/images/blog/workshop-update.JPEG b/images/blog/workshop-update.JPEG Binary files differnew file mode 100755 index 0000000..779928f --- /dev/null +++ b/images/blog/workshop-update.JPEG diff --git a/images/blog/workshop-update.webp b/images/blog/workshop-update.webp Binary files differnew file mode 100755 index 0000000..c1a30c3 --- /dev/null +++ b/images/blog/workshop-update.webp diff --git a/images/blog/wttr.JPEG b/images/blog/wttr.JPEG Binary files differnew file mode 100755 index 0000000..c5acd36 --- /dev/null +++ b/images/blog/wttr.JPEG diff --git a/images/blog/wttr.webp b/images/blog/wttr.webp Binary files differnew file mode 100755 index 0000000..60e57e7 --- /dev/null +++ b/images/blog/wttr.webp diff --git a/images/blog/x200.JPEG b/images/blog/x200.JPEG Binary files differnew file mode 100755 index 0000000..13c3d2b --- /dev/null +++ b/images/blog/x200.JPEG diff --git a/images/blog/x200.webp b/images/blog/x200.webp Binary files differnew file mode 100755 index 0000000..ae044ca --- /dev/null +++ b/images/blog/x200.webp diff --git a/images/blog/xfce4-panel-cpu-fan.png b/images/blog/xfce4-panel-cpu-fan.png Binary files differnew file mode 100755 index 0000000..a0c15ee --- /dev/null +++ b/images/blog/xfce4-panel-cpu-fan.png diff --git a/images/blog/zellij.JPEG b/images/blog/zellij.JPEG Binary files differnew file mode 100755 index 0000000..fa0a73a --- /dev/null +++ b/images/blog/zellij.JPEG diff --git a/images/blog/zellij.webp b/images/blog/zellij.webp Binary files differnew file mode 100755 index 0000000..2fdc50a --- /dev/null +++ b/images/blog/zellij.webp diff --git a/images/bloggings.jpg b/images/bloggings.jpg Binary files differnew file mode 100755 index 0000000..da03ed3 --- /dev/null +++ b/images/bloggings.jpg diff --git a/images/bonneannee.gif b/images/bonneannee.gif Binary files differnew file mode 100755 index 0000000..9eafaf0 --- /dev/null +++ b/images/bonneannee.gif diff --git a/images/bowl.jpg b/images/bowl.jpg Binary files differnew file mode 100755 index 0000000..c25934c --- /dev/null +++ b/images/bowl.jpg diff --git a/images/box-chevaux.JPEG b/images/box-chevaux.JPEG Binary files differnew file mode 100755 index 0000000..2298ef1 --- /dev/null +++ b/images/box-chevaux.JPEG diff --git a/images/box-chevaux.webp b/images/box-chevaux.webp Binary files differnew file mode 100755 index 0000000..855d5eb --- /dev/null +++ b/images/box-chevaux.webp diff --git a/images/browsers/dillo.jpg b/images/browsers/dillo.jpg Binary files differnew file mode 100755 index 0000000..144aa73 --- /dev/null +++ b/images/browsers/dillo.jpg diff --git a/images/browsers/firefox.jpg b/images/browsers/firefox.jpg Binary files differnew file mode 100755 index 0000000..144f55d --- /dev/null +++ b/images/browsers/firefox.jpg diff --git a/images/browsers/index.php b/images/browsers/index.php new file mode 100755 index 0000000..84c0b5c --- /dev/null +++ b/images/browsers/index.php @@ -0,0 +1,28 @@ +<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <link rel="stylesheet" href="/style.css" />
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <div class="center">
+ <h1>Bloggings</h1>
+ </div>
+ <a href="/index.php">Back</a>
+ <hr />
+ </header>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+</body>
+</html>
diff --git a/images/browsers/links.jpg b/images/browsers/links.jpg Binary files differnew file mode 100755 index 0000000..42c79a6 --- /dev/null +++ b/images/browsers/links.jpg diff --git a/images/browsers/lynx.jpg b/images/browsers/lynx.jpg Binary files differnew file mode 100755 index 0000000..3841b6f --- /dev/null +++ b/images/browsers/lynx.jpg diff --git a/images/browsers/w3m.jpg b/images/browsers/w3m.jpg Binary files differnew file mode 100755 index 0000000..d6886c9 --- /dev/null +++ b/images/browsers/w3m.jpg diff --git a/images/chantournage/animaux-ferme.webp b/images/chantournage/animaux-ferme.webp Binary files differnew file mode 100755 index 0000000..7ebf5e0 --- /dev/null +++ b/images/chantournage/animaux-ferme.webp diff --git a/images/chantournage/boite-coeur.webp b/images/chantournage/boite-coeur.webp Binary files differnew file mode 100755 index 0000000..083d34a --- /dev/null +++ b/images/chantournage/boite-coeur.webp diff --git a/images/chantournage/feuilles-plumes.webp b/images/chantournage/feuilles-plumes.webp Binary files differnew file mode 100755 index 0000000..1fb8e5c --- /dev/null +++ b/images/chantournage/feuilles-plumes.webp diff --git a/images/chantournage/fête-des-grandparents.webp b/images/chantournage/fête-des-grandparents.webp Binary files differnew file mode 100755 index 0000000..ead693b --- /dev/null +++ b/images/chantournage/fête-des-grandparents.webp diff --git a/images/chantournage/index.php b/images/chantournage/index.php new file mode 100755 index 0000000..2b9c6b5 --- /dev/null +++ b/images/chantournage/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/woodworking.html">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/chantournage/moto-chêne.webp b/images/chantournage/moto-chêne.webp Binary files differnew file mode 100755 index 0000000..4fe2f13 --- /dev/null +++ b/images/chantournage/moto-chêne.webp diff --git a/images/chantournage/sous-plat-chêne-33cm-triskele.webp b/images/chantournage/sous-plat-chêne-33cm-triskele.webp Binary files differnew file mode 100755 index 0000000..e1c789e --- /dev/null +++ b/images/chantournage/sous-plat-chêne-33cm-triskele.webp diff --git a/images/chantournage/sous-plat-hermine.webp b/images/chantournage/sous-plat-hermine.webp Binary files differnew file mode 100755 index 0000000..43026b2 --- /dev/null +++ b/images/chantournage/sous-plat-hermine.webp diff --git a/images/chantournage/triskele-cp-bouleau.webp b/images/chantournage/triskele-cp-bouleau.webp Binary files differnew file mode 100755 index 0000000..574201c --- /dev/null +++ b/images/chantournage/triskele-cp-bouleau.webp diff --git a/images/chimera.jpg b/images/chimera.jpg Binary files differnew file mode 100755 index 0000000..0462ea0 --- /dev/null +++ b/images/chimera.jpg diff --git a/images/classic-keyboard.JPEG b/images/classic-keyboard.JPEG Binary files differnew file mode 100755 index 0000000..ab45a6d --- /dev/null +++ b/images/classic-keyboard.JPEG diff --git a/images/classic-keyboard.webp b/images/classic-keyboard.webp Binary files differnew file mode 100755 index 0000000..4afa8e5 --- /dev/null +++ b/images/classic-keyboard.webp diff --git a/images/core-i5-cpu.JPEG b/images/core-i5-cpu.JPEG Binary files differnew file mode 100755 index 0000000..497e5aa --- /dev/null +++ b/images/core-i5-cpu.JPEG diff --git a/images/core-i5-cpu.png b/images/core-i5-cpu.png Binary files differnew file mode 100755 index 0000000..4d98615 --- /dev/null +++ b/images/core-i5-cpu.png diff --git a/images/core-i5-cpu.webp b/images/core-i5-cpu.webp Binary files differnew file mode 100755 index 0000000..62353d9 --- /dev/null +++ b/images/core-i5-cpu.webp diff --git a/images/coronet-herald.jpg b/images/coronet-herald.jpg Binary files differnew file mode 100755 index 0000000..c38786a --- /dev/null +++ b/images/coronet-herald.jpg diff --git a/images/corse-1975.jpg b/images/corse-1975.jpg Binary files differnew file mode 100755 index 0000000..35c41fb --- /dev/null +++ b/images/corse-1975.jpg diff --git a/images/dates-limites.jpg b/images/dates-limites.jpg Binary files differnew file mode 100755 index 0000000..b0c0b89 --- /dev/null +++ b/images/dates-limites.jpg diff --git a/images/debian-vs-arch.jpg b/images/debian-vs-arch.jpg Binary files differnew file mode 100755 index 0000000..c203e89 --- /dev/null +++ b/images/debian-vs-arch.jpg diff --git a/images/deltachatqr.jpg b/images/deltachatqr.jpg Binary files differnew file mode 100644 index 0000000..322b442 --- /dev/null +++ b/images/deltachatqr.jpg diff --git a/images/espion-dans-notre-poche.JPEG b/images/espion-dans-notre-poche.JPEG Binary files differnew file mode 100755 index 0000000..1fad281 --- /dev/null +++ b/images/espion-dans-notre-poche.JPEG diff --git a/images/espion-dans-notre-poche.webp b/images/espion-dans-notre-poche.webp Binary files differnew file mode 100755 index 0000000..33363f0 --- /dev/null +++ b/images/espion-dans-notre-poche.webp diff --git a/images/etau.JPEG b/images/etau.JPEG Binary files differnew file mode 100755 index 0000000..ca69d26 --- /dev/null +++ b/images/etau.JPEG diff --git a/images/etau.webp b/images/etau.webp Binary files differnew file mode 100755 index 0000000..b35400b --- /dev/null +++ b/images/etau.webp diff --git a/images/eu-flag.JPEG b/images/eu-flag.JPEG Binary files differnew file mode 100755 index 0000000..15285f2 --- /dev/null +++ b/images/eu-flag.JPEG diff --git a/images/eu-flag.webp b/images/eu-flag.webp Binary files differnew file mode 100755 index 0000000..487dfd4 --- /dev/null +++ b/images/eu-flag.webp diff --git a/images/evil-empire.jpg b/images/evil-empire.jpg Binary files differnew file mode 100755 index 0000000..78b3eb4 --- /dev/null +++ b/images/evil-empire.jpg diff --git a/images/firefox-drm.jpg b/images/firefox-drm.jpg Binary files differnew file mode 100755 index 0000000..45282ce --- /dev/null +++ b/images/firefox-drm.jpg diff --git a/images/first-box.JPEG b/images/first-box.JPEG Binary files differnew file mode 100755 index 0000000..3aa106d --- /dev/null +++ b/images/first-box.JPEG diff --git a/images/first-box.webp b/images/first-box.webp Binary files differnew file mode 100755 index 0000000..9f6f95c --- /dev/null +++ b/images/first-box.webp diff --git a/images/fugitive-telemetry.JPEG b/images/fugitive-telemetry.JPEG Binary files differnew file mode 100755 index 0000000..3a4c55b --- /dev/null +++ b/images/fugitive-telemetry.JPEG diff --git a/images/fugitive-telemetry.webp b/images/fugitive-telemetry.webp Binary files differnew file mode 100755 index 0000000..466185c --- /dev/null +++ b/images/fugitive-telemetry.webp diff --git a/images/grande-vase.JPEG b/images/grande-vase.JPEG Binary files differnew file mode 100755 index 0000000..2757b37 --- /dev/null +++ b/images/grande-vase.JPEG diff --git a/images/grande-vase.webp b/images/grande-vase.webp Binary files differnew file mode 100755 index 0000000..81f746c --- /dev/null +++ b/images/grande-vase.webp diff --git a/images/gsimplecal.JPEG b/images/gsimplecal.JPEG Binary files differnew file mode 100755 index 0000000..b642dac --- /dev/null +++ b/images/gsimplecal.JPEG diff --git a/images/gsimplecal.webp b/images/gsimplecal.webp Binary files differnew file mode 100755 index 0000000..8c7b997 --- /dev/null +++ b/images/gsimplecal.webp diff --git a/images/hail-mary-wier.JPEG b/images/hail-mary-wier.JPEG Binary files differnew file mode 100755 index 0000000..438e790 --- /dev/null +++ b/images/hail-mary-wier.JPEG diff --git a/images/hail-mary-wier.webp b/images/hail-mary-wier.webp Binary files differnew file mode 100755 index 0000000..2f7d8cc --- /dev/null +++ b/images/hail-mary-wier.webp diff --git a/images/happy-new-year.gif b/images/happy-new-year.gif Binary files differnew file mode 100755 index 0000000..6bc958e --- /dev/null +++ b/images/happy-new-year.gif diff --git a/images/histoire/1740.jpg b/images/histoire/1740.jpg Binary files differnew file mode 100755 index 0000000..d276f5c --- /dev/null +++ b/images/histoire/1740.jpg diff --git a/images/histoire/1866.jpg b/images/histoire/1866.jpg Binary files differnew file mode 100755 index 0000000..64a6a4f --- /dev/null +++ b/images/histoire/1866.jpg diff --git a/images/histoire/Allee-couverte-la-rocher.png b/images/histoire/Allee-couverte-la-rocher.png Binary files differnew file mode 100755 index 0000000..333d5b0 --- /dev/null +++ b/images/histoire/Allee-couverte-la-rocher.png diff --git a/images/histoire/Chêne-millénaire-saint-jacut.jpg b/images/histoire/Chêne-millénaire-saint-jacut.jpg Binary files differnew file mode 100755 index 0000000..c70fc94 --- /dev/null +++ b/images/histoire/Chêne-millénaire-saint-jacut.jpg diff --git a/images/histoire/Menhir-de-la-Touche-article.png b/images/histoire/Menhir-de-la-Touche-article.png Binary files differnew file mode 100755 index 0000000..3796188 --- /dev/null +++ b/images/histoire/Menhir-de-la-Touche-article.png diff --git a/images/histoire/arrete-confitures-1942.webp b/images/histoire/arrete-confitures-1942.webp Binary files differnew file mode 100755 index 0000000..f6b931e --- /dev/null +++ b/images/histoire/arrete-confitures-1942.webp diff --git a/images/histoire/bas_fournaux.jpg b/images/histoire/bas_fournaux.jpg Binary files differnew file mode 100755 index 0000000..6db8a0d --- /dev/null +++ b/images/histoire/bas_fournaux.jpg diff --git a/images/histoire/bourg-1824.png b/images/histoire/bourg-1824.png Binary files differnew file mode 100755 index 0000000..1dbf36f --- /dev/null +++ b/images/histoire/bourg-1824.png diff --git a/images/histoire/chateau-de-la-couaille.jpg b/images/histoire/chateau-de-la-couaille.jpg Binary files differnew file mode 100755 index 0000000..7d7e057 --- /dev/null +++ b/images/histoire/chateau-de-la-couaille.jpg diff --git a/images/histoire/cocantine.png b/images/histoire/cocantine.png Binary files differnew file mode 100755 index 0000000..175a2d9 --- /dev/null +++ b/images/histoire/cocantine.png diff --git a/images/histoire/cour-de-queneleue.png b/images/histoire/cour-de-queneleue.png Binary files differnew file mode 100755 index 0000000..7d39d92 --- /dev/null +++ b/images/histoire/cour-de-queneleue.png diff --git a/images/histoire/delivrance-cartes-alimentaires-1941.webp b/images/histoire/delivrance-cartes-alimentaires-1941.webp Binary files differnew file mode 100755 index 0000000..3a10012 --- /dev/null +++ b/images/histoire/delivrance-cartes-alimentaires-1941.webp diff --git a/images/histoire/eglise_saint_eutrope.jpg b/images/histoire/eglise_saint_eutrope.jpg Binary files differnew file mode 100755 index 0000000..8ac5bd1 --- /dev/null +++ b/images/histoire/eglise_saint_eutrope.jpg diff --git a/images/histoire/haches-langourla.png b/images/histoire/haches-langourla.png Binary files differnew file mode 100755 index 0000000..fda8902 --- /dev/null +++ b/images/histoire/haches-langourla.png diff --git a/images/histoire/index.php b/images/histoire/index.php new file mode 100755 index 0000000..276724b --- /dev/null +++ b/images/histoire/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/woodworking/index.php">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/histoire/vieux-chatet-de-coelan.png b/images/histoire/vieux-chatet-de-coelan.png Binary files differnew file mode 100755 index 0000000..eb2f821 --- /dev/null +++ b/images/histoire/vieux-chatet-de-coelan.png diff --git a/images/i3-blocks.JPEG b/images/i3-blocks.JPEG Binary files differnew file mode 100755 index 0000000..9a35cf8 --- /dev/null +++ b/images/i3-blocks.JPEG diff --git a/images/i3-blocks.webp b/images/i3-blocks.webp Binary files differnew file mode 100755 index 0000000..d31373d --- /dev/null +++ b/images/i3-blocks.webp diff --git a/images/index.php b/images/index.php new file mode 100755 index 0000000..84c0b5c --- /dev/null +++ b/images/index.php @@ -0,0 +1,28 @@ +<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <link rel="stylesheet" href="/style.css" />
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <div class="center">
+ <h1>Bloggings</h1>
+ </div>
+ <a href="/index.php">Back</a>
+ <hr />
+ </header>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+</body>
+</html>
diff --git a/images/informatique/amstrad-cpc-464.jpg b/images/informatique/amstrad-cpc-464.jpg Binary files differnew file mode 100755 index 0000000..592e135 --- /dev/null +++ b/images/informatique/amstrad-cpc-464.jpg diff --git a/images/informatique/burroghs_tc500.jpg b/images/informatique/burroghs_tc500.jpg Binary files differnew file mode 100755 index 0000000..de5633a --- /dev/null +++ b/images/informatique/burroghs_tc500.jpg diff --git a/images/informatique/ibmpc.jpg b/images/informatique/ibmpc.jpg Binary files differnew file mode 100755 index 0000000..2091f9e --- /dev/null +++ b/images/informatique/ibmpc.jpg diff --git a/images/informatique/index.php b/images/informatique/index.php new file mode 100755 index 0000000..276724b --- /dev/null +++ b/images/informatique/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/woodworking/index.php">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/informatique/noël-magasin.jpg b/images/informatique/noël-magasin.jpg Binary files differnew file mode 100755 index 0000000..06bc5f9 --- /dev/null +++ b/images/informatique/noël-magasin.jpg diff --git a/images/informatique/shop.jpg b/images/informatique/shop.jpg Binary files differnew file mode 100755 index 0000000..579be7a --- /dev/null +++ b/images/informatique/shop.jpg diff --git a/images/informatique/tandy1000hx.jpg b/images/informatique/tandy1000hx.jpg Binary files differnew file mode 100755 index 0000000..1302013 --- /dev/null +++ b/images/informatique/tandy1000hx.jpg diff --git a/images/informatique/zx81.jpg b/images/informatique/zx81.jpg Binary files differnew file mode 100755 index 0000000..ba481f0 --- /dev/null +++ b/images/informatique/zx81.jpg diff --git a/images/jersey-before.jpg b/images/jersey-before.jpg Binary files differnew file mode 100755 index 0000000..aa02ce4 --- /dev/null +++ b/images/jersey-before.jpg diff --git a/images/kobo-clara-colour.png b/images/kobo-clara-colour.png Binary files differnew file mode 100755 index 0000000..703b7c3 --- /dev/null +++ b/images/kobo-clara-colour.png diff --git a/images/la-grande-illusion.JPEG b/images/la-grande-illusion.JPEG Binary files differnew file mode 100755 index 0000000..9250c1d --- /dev/null +++ b/images/la-grande-illusion.JPEG diff --git a/images/la-grande-illusion.webp b/images/la-grande-illusion.webp Binary files differnew file mode 100755 index 0000000..9f1fa17 --- /dev/null +++ b/images/la-grande-illusion.webp diff --git a/images/langourla_map.jpg b/images/langourla_map.jpg Binary files differnew file mode 100755 index 0000000..bdba94e --- /dev/null +++ b/images/langourla_map.jpg diff --git a/images/linux-servers-cloud.jpg b/images/linux-servers-cloud.jpg Binary files differnew file mode 100755 index 0000000..4147240 --- /dev/null +++ b/images/linux-servers-cloud.jpg diff --git a/images/lnav.JPEG b/images/lnav.JPEG Binary files differnew file mode 100755 index 0000000..f357cc0 --- /dev/null +++ b/images/lnav.JPEG diff --git a/images/lnav.webp b/images/lnav.webp Binary files differnew file mode 100755 index 0000000..f4fc3ed --- /dev/null +++ b/images/lnav.webp diff --git a/images/logos/aposit.jpg b/images/logos/aposit.jpg Binary files differnew file mode 100755 index 0000000..cb46aba --- /dev/null +++ b/images/logos/aposit.jpg diff --git a/images/logos/applixware.jpg b/images/logos/applixware.jpg Binary files differnew file mode 100755 index 0000000..0b7e3e5 --- /dev/null +++ b/images/logos/applixware.jpg diff --git a/images/logos/avenir.jpg b/images/logos/avenir.jpg Binary files differnew file mode 100755 index 0000000..bb47a3c --- /dev/null +++ b/images/logos/avenir.jpg diff --git a/images/logos/emess.jpg b/images/logos/emess.jpg Binary files differnew file mode 100755 index 0000000..cf9b00a --- /dev/null +++ b/images/logos/emess.jpg diff --git a/images/logos/greenhouse.jpg b/images/logos/greenhouse.jpg Binary files differnew file mode 100755 index 0000000..af4c420 --- /dev/null +++ b/images/logos/greenhouse.jpg diff --git a/images/logos/idinan.jpg b/images/logos/idinan.jpg Binary files differnew file mode 100755 index 0000000..aa0a1de --- /dev/null +++ b/images/logos/idinan.jpg diff --git a/images/logos/index.php b/images/logos/index.php new file mode 100755 index 0000000..bc46e7a --- /dev/null +++ b/images/logos/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/le-mene.html">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/logos/langourlavie.jpg b/images/logos/langourlavie.jpg Binary files differnew file mode 100755 index 0000000..bd399ca --- /dev/null +++ b/images/logos/langourlavie.jpg diff --git a/images/logos/quilting.jpg b/images/logos/quilting.jpg Binary files differnew file mode 100755 index 0000000..b5d5902 --- /dev/null +++ b/images/logos/quilting.jpg diff --git a/images/logos/vistasource.jpg b/images/logos/vistasource.jpg Binary files differnew file mode 100755 index 0000000..70593c8 --- /dev/null +++ b/images/logos/vistasource.jpg diff --git a/images/lower-blade-guide-1.JPEG b/images/lower-blade-guide-1.JPEG Binary files differnew file mode 100755 index 0000000..90d3bfd --- /dev/null +++ b/images/lower-blade-guide-1.JPEG diff --git a/images/lower-blade-guide-1.webp b/images/lower-blade-guide-1.webp Binary files differnew file mode 100755 index 0000000..31c61d1 --- /dev/null +++ b/images/lower-blade-guide-1.webp diff --git a/images/mailcow-logo.png b/images/mailcow-logo.png Binary files differnew file mode 100755 index 0000000..3669cdc --- /dev/null +++ b/images/mailcow-logo.png diff --git a/images/maison-poupee.JPEG b/images/maison-poupee.JPEG Binary files differnew file mode 100755 index 0000000..ae7217a --- /dev/null +++ b/images/maison-poupee.JPEG diff --git a/images/maison-poupee.webp b/images/maison-poupee.webp Binary files differnew file mode 100755 index 0000000..32005a7 --- /dev/null +++ b/images/maison-poupee.webp diff --git a/images/me.jpg b/images/me.jpg Binary files differnew file mode 100755 index 0000000..d987a19 --- /dev/null +++ b/images/me.jpg diff --git a/images/mene/langourla/cartes-postales/1937-ancien-mairie.webp b/images/mene/langourla/cartes-postales/1937-ancien-mairie.webp Binary files differnew file mode 100755 index 0000000..84f29de --- /dev/null +++ b/images/mene/langourla/cartes-postales/1937-ancien-mairie.webp diff --git a/images/mene/langourla/cartes-postales/avenues-saint-joseph-2.webp b/images/mene/langourla/cartes-postales/avenues-saint-joseph-2.webp Binary files differnew file mode 100755 index 0000000..0b69ec8 --- /dev/null +++ b/images/mene/langourla/cartes-postales/avenues-saint-joseph-2.webp diff --git a/images/mene/langourla/cartes-postales/avenues-saint-joseph.webp b/images/mene/langourla/cartes-postales/avenues-saint-joseph.webp Binary files differnew file mode 100755 index 0000000..244c8fa --- /dev/null +++ b/images/mene/langourla/cartes-postales/avenues-saint-joseph.webp diff --git a/images/mene/langourla/cartes-postales/bourg-et-monument.webp b/images/mene/langourla/cartes-postales/bourg-et-monument.webp Binary files differnew file mode 100755 index 0000000..6acde74 --- /dev/null +++ b/images/mene/langourla/cartes-postales/bourg-et-monument.webp diff --git a/images/mene/langourla/cartes-postales/bourg.webp b/images/mene/langourla/cartes-postales/bourg.webp Binary files differnew file mode 100755 index 0000000..bc67d48 --- /dev/null +++ b/images/mene/langourla/cartes-postales/bourg.webp diff --git a/images/mene/langourla/cartes-postales/café-du-centre.webp b/images/mene/langourla/cartes-postales/café-du-centre.webp Binary files differnew file mode 100755 index 0000000..c02e25b --- /dev/null +++ b/images/mene/langourla/cartes-postales/café-du-centre.webp diff --git a/images/mene/langourla/cartes-postales/champ-du-chêne.webp b/images/mene/langourla/cartes-postales/champ-du-chêne.webp Binary files differnew file mode 100755 index 0000000..513bd22 --- /dev/null +++ b/images/mene/langourla/cartes-postales/champ-du-chêne.webp diff --git a/images/mene/langourla/cartes-postales/chapel-et-chêne.webp b/images/mene/langourla/cartes-postales/chapel-et-chêne.webp Binary files differnew file mode 100755 index 0000000..2dc5b22 --- /dev/null +++ b/images/mene/langourla/cartes-postales/chapel-et-chêne.webp diff --git a/images/mene/langourla/cartes-postales/chapelle-saint-joseph.webp b/images/mene/langourla/cartes-postales/chapelle-saint-joseph.webp Binary files differnew file mode 100755 index 0000000..6240a3f --- /dev/null +++ b/images/mene/langourla/cartes-postales/chapelle-saint-joseph.webp diff --git a/images/mene/langourla/cartes-postales/chateau-de-coelan.webp b/images/mene/langourla/cartes-postales/chateau-de-coelan.webp Binary files differnew file mode 100755 index 0000000..c395ead --- /dev/null +++ b/images/mene/langourla/cartes-postales/chateau-de-coelan.webp diff --git a/images/mene/langourla/cartes-postales/chêne-miraculeux.webp b/images/mene/langourla/cartes-postales/chêne-miraculeux.webp Binary files differnew file mode 100755 index 0000000..775e9ed --- /dev/null +++ b/images/mene/langourla/cartes-postales/chêne-miraculeux.webp diff --git a/images/mene/langourla/cartes-postales/ecole.webp b/images/mene/langourla/cartes-postales/ecole.webp Binary files differnew file mode 100755 index 0000000..5259f0c --- /dev/null +++ b/images/mene/langourla/cartes-postales/ecole.webp diff --git a/images/mene/langourla/cartes-postales/eglise.webp b/images/mene/langourla/cartes-postales/eglise.webp Binary files differnew file mode 100755 index 0000000..5b6edd8 --- /dev/null +++ b/images/mene/langourla/cartes-postales/eglise.webp diff --git a/images/mene/langourla/cartes-postales/index.php b/images/mene/langourla/cartes-postales/index.php new file mode 100755 index 0000000..bc46e7a --- /dev/null +++ b/images/mene/langourla/cartes-postales/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/le-mene.html">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/mene/langourla/cartes-postales/multivues.webp b/images/mene/langourla/cartes-postales/multivues.webp Binary files differnew file mode 100755 index 0000000..185dec0 --- /dev/null +++ b/images/mene/langourla/cartes-postales/multivues.webp diff --git a/images/mene/langourla/cartes-postales/rocher-le-celte.webp b/images/mene/langourla/cartes-postales/rocher-le-celte.webp Binary files differnew file mode 100755 index 0000000..bd34a4f --- /dev/null +++ b/images/mene/langourla/cartes-postales/rocher-le-celte.webp diff --git a/images/mene/langourla/cartes-postales/tour-saint-eutrop-et-cimitière.webp b/images/mene/langourla/cartes-postales/tour-saint-eutrop-et-cimitière.webp Binary files differnew file mode 100755 index 0000000..1f359a3 --- /dev/null +++ b/images/mene/langourla/cartes-postales/tour-saint-eutrop-et-cimitière.webp diff --git a/images/mene/langourla/cartes-postales/tour-saint-eutrop.webp b/images/mene/langourla/cartes-postales/tour-saint-eutrop.webp Binary files differnew file mode 100755 index 0000000..fdbae8b --- /dev/null +++ b/images/mene/langourla/cartes-postales/tour-saint-eutrop.webp diff --git a/images/mene/langourla/cartes-postales/vieux-moulin-de-cadeuc.webp b/images/mene/langourla/cartes-postales/vieux-moulin-de-cadeuc.webp Binary files differnew file mode 100755 index 0000000..1c0f5d5 --- /dev/null +++ b/images/mene/langourla/cartes-postales/vieux-moulin-de-cadeuc.webp diff --git a/images/mene/langourla/cartes-postales/villa-des-rosiers.webp b/images/mene/langourla/cartes-postales/villa-des-rosiers.webp Binary files differnew file mode 100755 index 0000000..2724fce --- /dev/null +++ b/images/mene/langourla/cartes-postales/villa-des-rosiers.webp diff --git a/images/mene/langourla/cartes-postales/vue-aerienne.webp b/images/mene/langourla/cartes-postales/vue-aerienne.webp Binary files differnew file mode 100755 index 0000000..4ed5052 --- /dev/null +++ b/images/mene/langourla/cartes-postales/vue-aerienne.webp diff --git a/images/mene/langourla/cartes-postales/vue-eglise.webp b/images/mene/langourla/cartes-postales/vue-eglise.webp Binary files differnew file mode 100755 index 0000000..419d5ac --- /dev/null +++ b/images/mene/langourla/cartes-postales/vue-eglise.webp diff --git a/images/mene/langourla/photos/Chemin-de-saint-gilles-à-Tangaie.webp b/images/mene/langourla/photos/Chemin-de-saint-gilles-à-Tangaie.webp Binary files differnew file mode 100755 index 0000000..e78d3c9 --- /dev/null +++ b/images/mene/langourla/photos/Chemin-de-saint-gilles-à-Tangaie.webp diff --git a/images/mene/langourla/photos/La-roche-de-la-Jument.webp b/images/mene/langourla/photos/La-roche-de-la-Jument.webp Binary files differnew file mode 100755 index 0000000..3f13d8e --- /dev/null +++ b/images/mene/langourla/photos/La-roche-de-la-Jument.webp diff --git a/images/mene/langourla/photos/Pied-de-boeuf-à-Tangaie.webp b/images/mene/langourla/photos/Pied-de-boeuf-à-Tangaie.webp Binary files differnew file mode 100755 index 0000000..9e7dea4 --- /dev/null +++ b/images/mene/langourla/photos/Pied-de-boeuf-à-Tangaie.webp diff --git a/images/mene/langourla/photos/index.php b/images/mene/langourla/photos/index.php new file mode 100755 index 0000000..bc46e7a --- /dev/null +++ b/images/mene/langourla/photos/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/le-mene.html">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/mene/logos/collinee.png b/images/mene/logos/collinee.png Binary files differnew file mode 100755 index 0000000..8f63ca2 --- /dev/null +++ b/images/mene/logos/collinee.png diff --git a/images/mene/logos/index.php b/images/mene/logos/index.php new file mode 100755 index 0000000..bc46e7a --- /dev/null +++ b/images/mene/logos/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/le-mene.html">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/mene/logos/langourla.png b/images/mene/logos/langourla.png Binary files differnew file mode 100755 index 0000000..387b070 --- /dev/null +++ b/images/mene/logos/langourla.png diff --git a/images/mene/logos/le gouray.png b/images/mene/logos/le gouray.png Binary files differnew file mode 100755 index 0000000..fba3b7b --- /dev/null +++ b/images/mene/logos/le gouray.png diff --git a/images/mene/logos/plessala.png b/images/mene/logos/plessala.png Binary files differnew file mode 100755 index 0000000..b76cbe3 --- /dev/null +++ b/images/mene/logos/plessala.png diff --git a/images/mene/logos/saint gilles.png b/images/mene/logos/saint gilles.png Binary files differnew file mode 100755 index 0000000..ff76a4e --- /dev/null +++ b/images/mene/logos/saint gilles.png diff --git a/images/mene/logos/saint goueno.png b/images/mene/logos/saint goueno.png Binary files differnew file mode 100755 index 0000000..2a55b22 --- /dev/null +++ b/images/mene/logos/saint goueno.png diff --git a/images/mene/logos/saint jacut du mené.png b/images/mene/logos/saint jacut du mené.png Binary files differnew file mode 100755 index 0000000..8b79a1b --- /dev/null +++ b/images/mene/logos/saint jacut du mené.png diff --git a/images/mene/saint-jacut-du-mene/cartes-postale b/images/mene/saint-jacut-du-mene/cartes-postale Binary files differnew file mode 100755 index 0000000..665191a --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postale diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/Allée-couverte-du-Rocher.webp b/images/mene/saint-jacut-du-mene/cartes-postales/Allée-couverte-du-Rocher.webp Binary files differnew file mode 100755 index 0000000..456a0b5 --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/Allée-couverte-du-Rocher.webp diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/Chêne-millénaire.webp b/images/mene/saint-jacut-du-mene/cartes-postales/Chêne-millénaire.webp Binary files differnew file mode 100755 index 0000000..e723a85 --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/Chêne-millénaire.webp diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/Menhir-de-la-Touche.webp b/images/mene/saint-jacut-du-mene/cartes-postales/Menhir-de-la-Touche.webp Binary files differnew file mode 100755 index 0000000..2577538 --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/Menhir-de-la-Touche.webp diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/chapelle-et-monument.webp b/images/mene/saint-jacut-du-mene/cartes-postales/chapelle-et-monument.webp Binary files differnew file mode 100755 index 0000000..825579d --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/chapelle-et-monument.webp diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/chapelle-n.d-de-bon-reconfort.webp b/images/mene/saint-jacut-du-mene/cartes-postales/chapelle-n.d-de-bon-reconfort.webp Binary files differnew file mode 100755 index 0000000..3f89634 --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/chapelle-n.d-de-bon-reconfort.webp diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/chapelle-saint-jean-baptiste.webp b/images/mene/saint-jacut-du-mene/cartes-postales/chapelle-saint-jean-baptiste.webp Binary files differnew file mode 100755 index 0000000..665191a --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/chapelle-saint-jean-baptiste.webp diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/chateau-du-parc.webp b/images/mene/saint-jacut-du-mene/cartes-postales/chateau-du-parc.webp Binary files differnew file mode 100755 index 0000000..883674c --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/chateau-du-parc.webp diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/eglise.webp b/images/mene/saint-jacut-du-mene/cartes-postales/eglise.webp Binary files differnew file mode 100755 index 0000000..54405a1 --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/eglise.webp diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/index.php b/images/mene/saint-jacut-du-mene/cartes-postales/index.php new file mode 100755 index 0000000..bc46e7a --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/le-mene.html">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/la-batterie.webp b/images/mene/saint-jacut-du-mene/cartes-postales/la-batterie.webp Binary files differnew file mode 100755 index 0000000..97cb955 --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/la-batterie.webp diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/recette-buraliste.webp b/images/mene/saint-jacut-du-mene/cartes-postales/recette-buraliste.webp Binary files differnew file mode 100755 index 0000000..e194abf --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/recette-buraliste.webp diff --git a/images/mene/saint-jacut-du-mene/cartes-postales/une-batterie.webp b/images/mene/saint-jacut-du-mene/cartes-postales/une-batterie.webp Binary files differnew file mode 100755 index 0000000..f7806f3 --- /dev/null +++ b/images/mene/saint-jacut-du-mene/cartes-postales/une-batterie.webp diff --git a/images/mene/saint-jacut-du-mene/photos/bois-du-moulin.webp b/images/mene/saint-jacut-du-mene/photos/bois-du-moulin.webp Binary files differnew file mode 100755 index 0000000..cc8c674 --- /dev/null +++ b/images/mene/saint-jacut-du-mene/photos/bois-du-moulin.webp diff --git a/images/mene/saint-jacut-du-mene/photos/chêne-du-parc-1.webp b/images/mene/saint-jacut-du-mene/photos/chêne-du-parc-1.webp Binary files differnew file mode 100755 index 0000000..e7dc4af --- /dev/null +++ b/images/mene/saint-jacut-du-mene/photos/chêne-du-parc-1.webp diff --git a/images/mene/saint-jacut-du-mene/photos/chêne-du-parc-2.webp b/images/mene/saint-jacut-du-mene/photos/chêne-du-parc-2.webp Binary files differnew file mode 100755 index 0000000..6f391d7 --- /dev/null +++ b/images/mene/saint-jacut-du-mene/photos/chêne-du-parc-2.webp diff --git a/images/mene/saint-jacut-du-mene/photos/chêne-du-parc-3.webp b/images/mene/saint-jacut-du-mene/photos/chêne-du-parc-3.webp Binary files differnew file mode 100755 index 0000000..0171f0a --- /dev/null +++ b/images/mene/saint-jacut-du-mene/photos/chêne-du-parc-3.webp diff --git a/images/mene/saint-jacut-du-mene/photos/eglise-vue-de-la-mairie.webp b/images/mene/saint-jacut-du-mene/photos/eglise-vue-de-la-mairie.webp Binary files differnew file mode 100755 index 0000000..528f392 --- /dev/null +++ b/images/mene/saint-jacut-du-mene/photos/eglise-vue-de-la-mairie.webp diff --git a/images/mene/saint-jacut-du-mene/photos/index.php b/images/mene/saint-jacut-du-mene/photos/index.php new file mode 100755 index 0000000..bc46e7a --- /dev/null +++ b/images/mene/saint-jacut-du-mene/photos/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/le-mene.html">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/menuiserie/abris jeux enfants.webp b/images/menuiserie/abris jeux enfants.webp Binary files differnew file mode 100755 index 0000000..0eb2269 --- /dev/null +++ b/images/menuiserie/abris jeux enfants.webp diff --git a/images/menuiserie/abris-jardin.webp b/images/menuiserie/abris-jardin.webp Binary files differnew file mode 100755 index 0000000..1f155b2 --- /dev/null +++ b/images/menuiserie/abris-jardin.webp diff --git a/images/menuiserie/abris-jeux-enfants.webp b/images/menuiserie/abris-jeux-enfants.webp Binary files differnew file mode 100755 index 0000000..0eb2269 --- /dev/null +++ b/images/menuiserie/abris-jeux-enfants.webp diff --git a/images/menuiserie/avion.webp b/images/menuiserie/avion.webp Binary files differnew file mode 100755 index 0000000..19dd53f --- /dev/null +++ b/images/menuiserie/avion.webp diff --git a/images/menuiserie/berceau-poupée.webp b/images/menuiserie/berceau-poupée.webp Binary files differnew file mode 100755 index 0000000..8264894 --- /dev/null +++ b/images/menuiserie/berceau-poupée.webp diff --git a/images/menuiserie/bonhomme-neige.webp b/images/menuiserie/bonhomme-neige.webp Binary files differnew file mode 100755 index 0000000..e3958e3 --- /dev/null +++ b/images/menuiserie/bonhomme-neige.webp diff --git a/images/menuiserie/chariot-scie-table.webp b/images/menuiserie/chariot-scie-table.webp Binary files differnew file mode 100755 index 0000000..91b1930 --- /dev/null +++ b/images/menuiserie/chariot-scie-table.webp diff --git a/images/menuiserie/compas-400mm.webp b/images/menuiserie/compas-400mm.webp Binary files differnew file mode 100755 index 0000000..5b425d6 --- /dev/null +++ b/images/menuiserie/compas-400mm.webp diff --git a/images/menuiserie/dominos-cumaru.jpg b/images/menuiserie/dominos-cumaru.jpg Binary files differnew file mode 100755 index 0000000..49e0d1f --- /dev/null +++ b/images/menuiserie/dominos-cumaru.jpg diff --git a/images/menuiserie/dominos.jpg b/images/menuiserie/dominos.jpg Binary files differnew file mode 100755 index 0000000..cf1fc57 --- /dev/null +++ b/images/menuiserie/dominos.jpg diff --git a/images/menuiserie/etau.webp b/images/menuiserie/etau.webp Binary files differnew file mode 100755 index 0000000..8c466d3 --- /dev/null +++ b/images/menuiserie/etau.webp diff --git a/images/menuiserie/index.php b/images/menuiserie/index.php new file mode 100755 index 0000000..2b9c6b5 --- /dev/null +++ b/images/menuiserie/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/woodworking.html">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/menuiserie/planche-en-bois-de-bout.webp b/images/menuiserie/planche-en-bois-de-bout.webp Binary files differnew file mode 100755 index 0000000..e6d2452 --- /dev/null +++ b/images/menuiserie/planche-en-bois-de-bout.webp diff --git a/images/menuiserie/support-roulettes.webp b/images/menuiserie/support-roulettes.webp Binary files differnew file mode 100755 index 0000000..41150e5 --- /dev/null +++ b/images/menuiserie/support-roulettes.webp diff --git a/images/menuiserie/table-atelier.webp b/images/menuiserie/table-atelier.webp Binary files differnew file mode 100755 index 0000000..05a6228 --- /dev/null +++ b/images/menuiserie/table-atelier.webp diff --git a/images/menuiserie/voiture-oliver.webp b/images/menuiserie/voiture-oliver.webp Binary files differnew file mode 100755 index 0000000..a88c51e --- /dev/null +++ b/images/menuiserie/voiture-oliver.webp diff --git a/images/mer-des-faluns.jpg b/images/mer-des-faluns.jpg Binary files differnew file mode 100755 index 0000000..0ad149d --- /dev/null +++ b/images/mer-des-faluns.jpg diff --git a/images/mini-chariot.JPEG b/images/mini-chariot.JPEG Binary files differnew file mode 100755 index 0000000..769522e --- /dev/null +++ b/images/mini-chariot.JPEG diff --git a/images/mini-chariot.webp b/images/mini-chariot.webp Binary files differnew file mode 100755 index 0000000..8096ef6 --- /dev/null +++ b/images/mini-chariot.webp diff --git a/images/mugshot-02-12-2023.png b/images/mugshot-02-12-2023.png Binary files differnew file mode 100755 index 0000000..3f6f07c --- /dev/null +++ b/images/mugshot-02-12-2023.png diff --git a/images/mugshot-09-11-2023.jpg b/images/mugshot-09-11-2023.jpg Binary files differnew file mode 100755 index 0000000..d9aeac2 --- /dev/null +++ b/images/mugshot-09-11-2023.jpg diff --git a/images/new-bench.JPEG b/images/new-bench.JPEG Binary files differnew file mode 100755 index 0000000..ac233bc --- /dev/null +++ b/images/new-bench.JPEG diff --git a/images/new-bench.webp b/images/new-bench.webp Binary files differnew file mode 100755 index 0000000..777a977 --- /dev/null +++ b/images/new-bench.webp diff --git a/images/new-oland-tool.jpg b/images/new-oland-tool.jpg Binary files differnew file mode 100755 index 0000000..899afc6 --- /dev/null +++ b/images/new-oland-tool.jpg diff --git a/images/oak-bowl-28cm.JPEG b/images/oak-bowl-28cm.JPEG Binary files differnew file mode 100755 index 0000000..bba23d3 --- /dev/null +++ b/images/oak-bowl-28cm.JPEG diff --git a/images/oak-bowl-28cm.webp b/images/oak-bowl-28cm.webp Binary files differnew file mode 100755 index 0000000..9f981c5 --- /dev/null +++ b/images/oak-bowl-28cm.webp diff --git a/images/obscurantism.jpg b/images/obscurantism.jpg Binary files differnew file mode 100755 index 0000000..d1f136b --- /dev/null +++ b/images/obscurantism.jpg diff --git a/images/odysee.JPEG b/images/odysee.JPEG Binary files differnew file mode 100755 index 0000000..49d9504 --- /dev/null +++ b/images/odysee.JPEG diff --git a/images/odysee.webp b/images/odysee.webp Binary files differnew file mode 100755 index 0000000..56c31a7 --- /dev/null +++ b/images/odysee.webp diff --git a/images/oland-tools.jpg b/images/oland-tools.jpg Binary files differnew file mode 100755 index 0000000..0112ba5 --- /dev/null +++ b/images/oland-tools.jpg diff --git a/images/pense-bete.JPEG b/images/pense-bete.JPEG Binary files differnew file mode 100755 index 0000000..a1d6249 --- /dev/null +++ b/images/pense-bete.JPEG diff --git a/images/pense-bete.webp b/images/pense-bete.webp Binary files differnew file mode 100755 index 0000000..d7e3623 --- /dev/null +++ b/images/pense-bete.webp diff --git a/images/phil_from_france.jpg b/images/phil_from_france.jpg Binary files differnew file mode 100755 index 0000000..27a2c57 --- /dev/null +++ b/images/phil_from_france.jpg diff --git a/images/planche-à-découper-jacqueline.JPEG b/images/planche-à-découper-jacqueline.JPEG Binary files differnew file mode 100755 index 0000000..4045295 --- /dev/null +++ b/images/planche-à-découper-jacqueline.JPEG diff --git a/images/planche-à-découper-jacqueline.webp b/images/planche-à-découper-jacqueline.webp Binary files differnew file mode 100755 index 0000000..24b5cf0 --- /dev/null +++ b/images/planche-à-découper-jacqueline.webp diff --git a/images/prettier.jpg b/images/prettier.jpg Binary files differnew file mode 100755 index 0000000..b8e46e6 --- /dev/null +++ b/images/prettier.jpg diff --git a/images/pyradio.JPEG b/images/pyradio.JPEG Binary files differnew file mode 100755 index 0000000..91b23af --- /dev/null +++ b/images/pyradio.JPEG diff --git a/images/pyradio.webp b/images/pyradio.webp Binary files differnew file mode 100755 index 0000000..0fbc35b --- /dev/null +++ b/images/pyradio.webp diff --git a/images/pyrogravure/chaise-clara.webp b/images/pyrogravure/chaise-clara.webp Binary files differnew file mode 100755 index 0000000..791068c --- /dev/null +++ b/images/pyrogravure/chaise-clara.webp diff --git a/images/pyrogravure/chestnut-bowl.webp b/images/pyrogravure/chestnut-bowl.webp Binary files differnew file mode 100755 index 0000000..6fbd5f2 --- /dev/null +++ b/images/pyrogravure/chestnut-bowl.webp diff --git a/images/pyrogravure/graines-tonton-loïc.webp b/images/pyrogravure/graines-tonton-loïc.webp Binary files differnew file mode 100755 index 0000000..437235f --- /dev/null +++ b/images/pyrogravure/graines-tonton-loïc.webp diff --git a/images/pyrogravure/guitar-ian.webp b/images/pyrogravure/guitar-ian.webp Binary files differnew file mode 100755 index 0000000..b0dbb38 --- /dev/null +++ b/images/pyrogravure/guitar-ian.webp diff --git a/images/pyrogravure/index.php b/images/pyrogravure/index.php new file mode 100755 index 0000000..2b9c6b5 --- /dev/null +++ b/images/pyrogravure/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/woodworking.html">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/pyrogravure/logo-fer-à-marquer.webp b/images/pyrogravure/logo-fer-à-marquer.webp Binary files differnew file mode 100755 index 0000000..321ee8e --- /dev/null +++ b/images/pyrogravure/logo-fer-à-marquer.webp diff --git a/images/pyrogravure/triskel-sous-plat-signé.webp b/images/pyrogravure/triskel-sous-plat-signé.webp Binary files differnew file mode 100755 index 0000000..6a1d2e5 --- /dev/null +++ b/images/pyrogravure/triskel-sous-plat-signé.webp diff --git a/images/pyrogravure/vase-chêne-décoré.webp b/images/pyrogravure/vase-chêne-décoré.webp Binary files differnew file mode 100755 index 0000000..98e58a8 --- /dev/null +++ b/images/pyrogravure/vase-chêne-décoré.webp diff --git a/images/recordpower_AC400.jpg b/images/recordpower_AC400.jpg Binary files differnew file mode 100755 index 0000000..216f186 --- /dev/null +++ b/images/recordpower_AC400.jpg diff --git a/images/riddings-web-site.JPEG b/images/riddings-web-site.JPEG Binary files differnew file mode 100755 index 0000000..aa49850 --- /dev/null +++ b/images/riddings-web-site.JPEG diff --git a/images/riddings-web-site.webp b/images/riddings-web-site.webp Binary files differnew file mode 100755 index 0000000..4fbad6e --- /dev/null +++ b/images/riddings-web-site.webp diff --git a/images/rss.png b/images/rss.png Binary files differnew file mode 100755 index 0000000..60ac9e4 --- /dev/null +++ b/images/rss.png diff --git a/images/saint-catherine.jpg b/images/saint-catherine.jpg Binary files differnew file mode 100755 index 0000000..8ce7145 --- /dev/null +++ b/images/saint-catherine.jpg diff --git a/images/scie-alignment-1.jpg b/images/scie-alignment-1.jpg Binary files differnew file mode 100755 index 0000000..28027d5 --- /dev/null +++ b/images/scie-alignment-1.jpg diff --git a/images/scie-alignment-2.jpg b/images/scie-alignment-2.jpg Binary files differnew file mode 100755 index 0000000..03e8573 --- /dev/null +++ b/images/scie-alignment-2.jpg diff --git a/images/scie-completed-1.jpg b/images/scie-completed-1.jpg Binary files differnew file mode 100755 index 0000000..d345f57 --- /dev/null +++ b/images/scie-completed-1.jpg diff --git a/images/scie-completed-2.jpg b/images/scie-completed-2.jpg Binary files differnew file mode 100755 index 0000000..b7d1d67 --- /dev/null +++ b/images/scie-completed-2.jpg diff --git a/images/scie-completed-3.jpg b/images/scie-completed-3.jpg Binary files differnew file mode 100755 index 0000000..b7694a8 --- /dev/null +++ b/images/scie-completed-3.jpg diff --git a/images/scie-completed-4.jpg b/images/scie-completed-4.jpg Binary files differnew file mode 100755 index 0000000..8aeea49 --- /dev/null +++ b/images/scie-completed-4.jpg diff --git a/images/scie-completed-5.jpg b/images/scie-completed-5.jpg Binary files differnew file mode 100755 index 0000000..a63b10f --- /dev/null +++ b/images/scie-completed-5.jpg diff --git a/images/scie-frame-1.jpg b/images/scie-frame-1.jpg Binary files differnew file mode 100755 index 0000000..ee97aa9 --- /dev/null +++ b/images/scie-frame-1.jpg diff --git a/images/scie-frame-2.jpg b/images/scie-frame-2.jpg Binary files differnew file mode 100755 index 0000000..b3213e7 --- /dev/null +++ b/images/scie-frame-2.jpg diff --git a/images/scie-frame-3.jpg b/images/scie-frame-3.jpg Binary files differnew file mode 100755 index 0000000..2e98592 --- /dev/null +++ b/images/scie-frame-3.jpg diff --git a/images/scie-frame-4.jpg b/images/scie-frame-4.jpg Binary files differnew file mode 100755 index 0000000..06b0856 --- /dev/null +++ b/images/scie-frame-4.jpg diff --git a/images/scie-improvement-1.jpg b/images/scie-improvement-1.jpg Binary files differnew file mode 100755 index 0000000..03e8573 --- /dev/null +++ b/images/scie-improvement-1.jpg diff --git a/images/scie-parts-1.jpg b/images/scie-parts-1.jpg Binary files differnew file mode 100755 index 0000000..bfc828c --- /dev/null +++ b/images/scie-parts-1.jpg diff --git a/images/scie-wheel-1.jpg b/images/scie-wheel-1.jpg Binary files differnew file mode 100755 index 0000000..f69d073 --- /dev/null +++ b/images/scie-wheel-1.jpg diff --git a/images/screw-jig.JPEG b/images/screw-jig.JPEG Binary files differnew file mode 100755 index 0000000..4254dcf --- /dev/null +++ b/images/screw-jig.JPEG diff --git a/images/screw-jig.webp b/images/screw-jig.webp Binary files differnew file mode 100755 index 0000000..3713f68 --- /dev/null +++ b/images/screw-jig.webp diff --git a/images/searxng.jpg b/images/searxng.jpg Binary files differnew file mode 100755 index 0000000..514de4a --- /dev/null +++ b/images/searxng.jpg diff --git a/images/shadocks-pumping-300.gif b/images/shadocks-pumping-300.gif Binary files differnew file mode 100755 index 0000000..8c5e42b --- /dev/null +++ b/images/shadocks-pumping-300.gif diff --git a/images/shadocks-pumping.gif b/images/shadocks-pumping.gif Binary files differnew file mode 100755 index 0000000..922bf5a --- /dev/null +++ b/images/shadocks-pumping.gif diff --git a/images/shadocks.gif b/images/shadocks.gif Binary files differnew file mode 100644 index 0000000..a055ad0 --- /dev/null +++ b/images/shadocks.gif diff --git a/images/shop.jpg b/images/shop.jpg Binary files differnew file mode 100755 index 0000000..f23209b --- /dev/null +++ b/images/shop.jpg diff --git a/images/sous-plat-incrustation.JPEG b/images/sous-plat-incrustation.JPEG Binary files differnew file mode 100755 index 0000000..a1524ae --- /dev/null +++ b/images/sous-plat-incrustation.JPEG diff --git a/images/sous-plat-incrustation.webp b/images/sous-plat-incrustation.webp Binary files differnew file mode 100755 index 0000000..2c56c29 --- /dev/null +++ b/images/sous-plat-incrustation.webp diff --git a/images/stop-block.JPEG b/images/stop-block.JPEG Binary files differnew file mode 100755 index 0000000..812861e --- /dev/null +++ b/images/stop-block.JPEG diff --git a/images/stop-block.webp b/images/stop-block.webp Binary files differnew file mode 100755 index 0000000..71fdbc6 --- /dev/null +++ b/images/stop-block.webp diff --git a/images/support-roue-superior.JPEG b/images/support-roue-superior.JPEG Binary files differnew file mode 100755 index 0000000..93105bb --- /dev/null +++ b/images/support-roue-superior.JPEG diff --git a/images/support-roue-superior.webp b/images/support-roue-superior.webp Binary files differnew file mode 100755 index 0000000..d93aac2 --- /dev/null +++ b/images/support-roue-superior.webp diff --git a/images/support-trunnion.JPEG b/images/support-trunnion.JPEG Binary files differnew file mode 100755 index 0000000..844b449 --- /dev/null +++ b/images/support-trunnion.JPEG diff --git a/images/support-trunnion.webp b/images/support-trunnion.webp Binary files differnew file mode 100755 index 0000000..3f28a08 --- /dev/null +++ b/images/support-trunnion.webp diff --git a/images/systemcollapse.jpg b/images/systemcollapse.jpg Binary files differnew file mode 100755 index 0000000..f37165d --- /dev/null +++ b/images/systemcollapse.jpg diff --git a/images/t430-cpu-upgrade.jpg b/images/t430-cpu-upgrade.jpg Binary files differnew file mode 100755 index 0000000..613d1d5 --- /dev/null +++ b/images/t430-cpu-upgrade.jpg diff --git a/images/table-support-trunions.JPEG b/images/table-support-trunions.JPEG Binary files differnew file mode 100755 index 0000000..7f3da60 --- /dev/null +++ b/images/table-support-trunions.JPEG diff --git a/images/table-support-trunions.webp b/images/table-support-trunions.webp Binary files differnew file mode 100755 index 0000000..086723b --- /dev/null +++ b/images/table-support-trunions.webp diff --git a/images/tcl.webp b/images/tcl.webp Binary files differnew file mode 100755 index 0000000..23c6958 --- /dev/null +++ b/images/tcl.webp diff --git a/images/thinkpad-x220-mic-button.JPEG b/images/thinkpad-x220-mic-button.JPEG Binary files differnew file mode 100755 index 0000000..d9179b9 --- /dev/null +++ b/images/thinkpad-x220-mic-button.JPEG diff --git a/images/thinkpad-x220-mic-button.webp b/images/thinkpad-x220-mic-button.webp Binary files differnew file mode 100755 index 0000000..db15d4b --- /dev/null +++ b/images/thinkpad-x220-mic-button.webp diff --git a/images/tour/1er-idée.webp b/images/tour/1er-idée.webp Binary files differnew file mode 100755 index 0000000..30af204 --- /dev/null +++ b/images/tour/1er-idée.webp diff --git a/images/tour/2ième-idée.webp b/images/tour/2ième-idée.webp Binary files differnew file mode 100755 index 0000000..ff63eab --- /dev/null +++ b/images/tour/2ième-idée.webp diff --git a/images/tour/3ième-idée.webp b/images/tour/3ième-idée.webp Binary files differnew file mode 100755 index 0000000..ec914f3 --- /dev/null +++ b/images/tour/3ième-idée.webp diff --git a/images/tour/4-poupée-fixe.webp b/images/tour/4-poupée-fixe.webp Binary files differnew file mode 100755 index 0000000..f042985 --- /dev/null +++ b/images/tour/4-poupée-fixe.webp diff --git a/images/tour/5-porte-outil-poupée-mobile.webp b/images/tour/5-porte-outil-poupée-mobile.webp Binary files differnew file mode 100755 index 0000000..ad2859f --- /dev/null +++ b/images/tour/5-porte-outil-poupée-mobile.webp diff --git a/images/tour/6-idée-poupée-mobile.webp b/images/tour/6-idée-poupée-mobile.webp Binary files differnew file mode 100755 index 0000000..159131e --- /dev/null +++ b/images/tour/6-idée-poupée-mobile.webp diff --git a/images/tour/7-poupée-mobile.webp b/images/tour/7-poupée-mobile.webp Binary files differnew file mode 100755 index 0000000..13eb5f2 --- /dev/null +++ b/images/tour/7-poupée-mobile.webp diff --git a/images/tour/8-contre-pointe-tournante.webp b/images/tour/8-contre-pointe-tournante.webp Binary files differnew file mode 100755 index 0000000..2456156 --- /dev/null +++ b/images/tour/8-contre-pointe-tournante.webp diff --git a/images/tour/9-ecrou-de-blocage.webp b/images/tour/9-ecrou-de-blocage.webp Binary files differnew file mode 100755 index 0000000..884ea52 --- /dev/null +++ b/images/tour/9-ecrou-de-blocage.webp diff --git a/images/tour/91-cliquet-blocage-poupée-mobile.webp b/images/tour/91-cliquet-blocage-poupée-mobile.webp Binary files differnew file mode 100755 index 0000000..5e7591e --- /dev/null +++ b/images/tour/91-cliquet-blocage-poupée-mobile.webp diff --git a/images/tour/92-broche.webp b/images/tour/92-broche.webp Binary files differnew file mode 100755 index 0000000..d5f8792 --- /dev/null +++ b/images/tour/92-broche.webp diff --git a/images/tour/93-assemblage.webp b/images/tour/93-assemblage.webp Binary files differnew file mode 100755 index 0000000..fd630cb --- /dev/null +++ b/images/tour/93-assemblage.webp diff --git a/images/tour/931-serrage--porte-outils.webp b/images/tour/931-serrage--porte-outils.webp Binary files differnew file mode 100755 index 0000000..a7ec966 --- /dev/null +++ b/images/tour/931-serrage--porte-outils.webp diff --git a/images/tour/932-blocage-broche.webp b/images/tour/932-blocage-broche.webp Binary files differnew file mode 100755 index 0000000..4e20576 --- /dev/null +++ b/images/tour/932-blocage-broche.webp diff --git a/images/tour/94-300-tpm.webp b/images/tour/94-300-tpm.webp Binary files differnew file mode 100755 index 0000000..d8ce360 --- /dev/null +++ b/images/tour/94-300-tpm.webp diff --git a/images/tour/95-600-tpm.webp b/images/tour/95-600-tpm.webp Binary files differnew file mode 100755 index 0000000..cba4560 --- /dev/null +++ b/images/tour/95-600-tpm.webp diff --git a/images/tour/96-1400-tpm.webp b/images/tour/96-1400-tpm.webp Binary files differnew file mode 100755 index 0000000..8bb0c9a --- /dev/null +++ b/images/tour/96-1400-tpm.webp diff --git a/images/tour/97-finale.webp b/images/tour/97-finale.webp Binary files differnew file mode 100755 index 0000000..3bb4949 --- /dev/null +++ b/images/tour/97-finale.webp diff --git a/images/tour/index.php b/images/tour/index.php new file mode 100755 index 0000000..276724b --- /dev/null +++ b/images/tour/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/woodworking/index.php">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/tournages/3-bols-chataigner.webp b/images/tournages/3-bols-chataigner.webp Binary files differnew file mode 100755 index 0000000..58443b5 --- /dev/null +++ b/images/tournages/3-bols-chataigner.webp diff --git a/images/tournages/4-bols-cerisier.webp b/images/tournages/4-bols-cerisier.webp Binary files differnew file mode 100755 index 0000000..3157088 --- /dev/null +++ b/images/tournages/4-bols-cerisier.webp diff --git a/images/tournages/Pot-en-charme.webp b/images/tournages/Pot-en-charme.webp Binary files differnew file mode 100755 index 0000000..6789136 --- /dev/null +++ b/images/tournages/Pot-en-charme.webp diff --git a/images/tournages/bol-cerisier.webp b/images/tournages/bol-cerisier.webp Binary files differnew file mode 100755 index 0000000..93290af --- /dev/null +++ b/images/tournages/bol-cerisier.webp diff --git a/images/tournages/bol-châtaigner.webp b/images/tournages/bol-châtaigner.webp Binary files differnew file mode 100755 index 0000000..4191f1d --- /dev/null +++ b/images/tournages/bol-châtaigner.webp diff --git a/images/tournages/bol-hêtre-12-02-20.webp b/images/tournages/bol-hêtre-12-02-20.webp Binary files differnew file mode 100755 index 0000000..ba95906 --- /dev/null +++ b/images/tournages/bol-hêtre-12-02-20.webp diff --git a/images/tournages/bol-hêtre-15-02-20.webp b/images/tournages/bol-hêtre-15-02-20.webp Binary files differnew file mode 100755 index 0000000..a3425f9 --- /dev/null +++ b/images/tournages/bol-hêtre-15-02-20.webp diff --git a/images/tournages/bol-hêtre-21-02-20.webp b/images/tournages/bol-hêtre-21-02-20.webp Binary files differnew file mode 100755 index 0000000..b68890f --- /dev/null +++ b/images/tournages/bol-hêtre-21-02-20.webp diff --git a/images/tournages/bol-hêtre-epoxy.webp b/images/tournages/bol-hêtre-epoxy.webp Binary files differnew file mode 100755 index 0000000..b314e15 --- /dev/null +++ b/images/tournages/bol-hêtre-epoxy.webp diff --git a/images/tournages/coquetiers-modernes.webp b/images/tournages/coquetiers-modernes.webp Binary files differnew file mode 100755 index 0000000..e2becf0 --- /dev/null +++ b/images/tournages/coquetiers-modernes.webp diff --git a/images/tournages/dauphin.webp b/images/tournages/dauphin.webp Binary files differnew file mode 100755 index 0000000..f24816c --- /dev/null +++ b/images/tournages/dauphin.webp diff --git a/images/tournages/deux-vases-thuya.webp b/images/tournages/deux-vases-thuya.webp Binary files differnew file mode 100755 index 0000000..9962443 --- /dev/null +++ b/images/tournages/deux-vases-thuya.webp diff --git a/images/tournages/ebauche-bol-30cm.webp b/images/tournages/ebauche-bol-30cm.webp Binary files differnew file mode 100755 index 0000000..85bb874 --- /dev/null +++ b/images/tournages/ebauche-bol-30cm.webp diff --git a/images/tournages/expérimentation.webp b/images/tournages/expérimentation.webp Binary files differnew file mode 100755 index 0000000..28d197b --- /dev/null +++ b/images/tournages/expérimentation.webp diff --git a/images/tournages/fleurs.webp b/images/tournages/fleurs.webp Binary files differnew file mode 100755 index 0000000..a135cd5 --- /dev/null +++ b/images/tournages/fleurs.webp diff --git a/images/tournages/flowers.webp b/images/tournages/flowers.webp Binary files differnew file mode 100755 index 0000000..22f0351 --- /dev/null +++ b/images/tournages/flowers.webp diff --git a/images/tournages/grande-vase.webp b/images/tournages/grande-vase.webp Binary files differnew file mode 100755 index 0000000..25122c3 --- /dev/null +++ b/images/tournages/grande-vase.webp diff --git a/images/tournages/index.php b/images/tournages/index.php new file mode 100755 index 0000000..2b9c6b5 --- /dev/null +++ b/images/tournages/index.php @@ -0,0 +1,33 @@ +<!doctype html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" >
+ <link rel="stylesheet" href="/style.css" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
+ <title>Bloggings</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/static/woodworking.html">Back</a>
+ </header>
+ <main>
+ <article>
+<h2><?php echo dirname($_SERVER['SCRIPT_NAME']);?></h2>
+ <?php
+ $images = glob("./*.{jpg,jpeg,gif,png,bmp,webp,webP,svg}", GLOB_BRACE);
+ sort ($images);
+ foreach ($images as $i) {
+ $img = basename($i);
+ $caption = ucfirst(substr($img, 0, strrpos($img, ".")));
+ printf("<figure><img src='./%s'/><figcaption>%s</figcaption></figure>", $img, $caption);
+ }
+ ?>
+ </article>
+ </main>
+ <footer>
+ <p>§</p>
+ </footer>
+</body>
+</html>
diff --git a/images/tournages/mortier-cerisier.webp b/images/tournages/mortier-cerisier.webp Binary files differnew file mode 100755 index 0000000..2ad675b --- /dev/null +++ b/images/tournages/mortier-cerisier.webp diff --git a/images/tournages/mortier-pilon-cerisier.webp b/images/tournages/mortier-pilon-cerisier.webp Binary files differnew file mode 100755 index 0000000..a407fa0 --- /dev/null +++ b/images/tournages/mortier-pilon-cerisier.webp diff --git a/images/tournages/oak-bowl-28cm.webp b/images/tournages/oak-bowl-28cm.webp Binary files differnew file mode 100755 index 0000000..7a5beb3 --- /dev/null +++ b/images/tournages/oak-bowl-28cm.webp diff --git a/images/tournages/oak_bowl_experiment_1.webp b/images/tournages/oak_bowl_experiment_1.webp Binary files differnew file mode 100755 index 0000000..c17bf72 --- /dev/null +++ b/images/tournages/oak_bowl_experiment_1.webp diff --git a/images/tournages/petit-vase-thuya.webp b/images/tournages/petit-vase-thuya.webp Binary files differnew file mode 100755 index 0000000..065b53a --- /dev/null +++ b/images/tournages/petit-vase-thuya.webp diff --git a/images/tournages/plat-ceriser.webp b/images/tournages/plat-ceriser.webp Binary files differnew file mode 100755 index 0000000..d161de4 --- /dev/null +++ b/images/tournages/plat-ceriser.webp diff --git a/images/tournages/porte-bougie-1.jpg b/images/tournages/porte-bougie-1.jpg Binary files differnew file mode 100755 index 0000000..f3e5d7e --- /dev/null +++ b/images/tournages/porte-bougie-1.jpg diff --git a/images/tournages/pot-charme-pyro.webp b/images/tournages/pot-charme-pyro.webp Binary files differnew file mode 100755 index 0000000..873c775 --- /dev/null +++ b/images/tournages/pot-charme-pyro.webp diff --git a/images/tournages/pot-open.webp b/images/tournages/pot-open.webp Binary files differnew file mode 100755 index 0000000..091c0f3 --- /dev/null +++ b/images/tournages/pot-open.webp diff --git a/images/tournages/pot.webp b/images/tournages/pot.webp Binary files differnew file mode 100755 index 0000000..113e6bf --- /dev/null +++ b/images/tournages/pot.webp diff --git a/images/tournages/tall-vase.webp b/images/tournages/tall-vase.webp Binary files differnew file mode 100755 index 0000000..9041649 --- /dev/null +++ b/images/tournages/tall-vase.webp diff --git a/images/tournages/two-vases.webp b/images/tournages/two-vases.webp Binary files differnew file mode 100755 index 0000000..07bb549 --- /dev/null +++ b/images/tournages/two-vases.webp diff --git a/images/tournages/vase-chêne.webp b/images/tournages/vase-chêne.webp Binary files differnew file mode 100755 index 0000000..db5fb67 --- /dev/null +++ b/images/tournages/vase-chêne.webp diff --git a/images/tournages/vase-moabi.webp b/images/tournages/vase-moabi.webp Binary files differnew file mode 100755 index 0000000..0ff0850 --- /dev/null +++ b/images/tournages/vase-moabi.webp diff --git a/images/tournages/vase-thuya.webp b/images/tournages/vase-thuya.webp Binary files differnew file mode 100755 index 0000000..a134e3e --- /dev/null +++ b/images/tournages/vase-thuya.webp diff --git a/images/tprg.JPEG b/images/tprg.JPEG Binary files differnew file mode 100755 index 0000000..d9fe62c --- /dev/null +++ b/images/tprg.JPEG diff --git a/images/tprg.webp b/images/tprg.webp Binary files differnew file mode 100755 index 0000000..7b974c1 --- /dev/null +++ b/images/tprg.webp diff --git a/images/trunion-shim.JPEG b/images/trunion-shim.JPEG Binary files differnew file mode 100755 index 0000000..abc3bb3 --- /dev/null +++ b/images/trunion-shim.JPEG diff --git a/images/trunion-shim.webp b/images/trunion-shim.webp Binary files differnew file mode 100755 index 0000000..6dee626 --- /dev/null +++ b/images/trunion-shim.webp diff --git a/images/trunions-chevilles.JPEG b/images/trunions-chevilles.JPEG Binary files differnew file mode 100755 index 0000000..b33f335 --- /dev/null +++ b/images/trunions-chevilles.JPEG diff --git a/images/trunions-chevilles.webp b/images/trunions-chevilles.webp Binary files differnew file mode 100755 index 0000000..7fbf818 --- /dev/null +++ b/images/trunions-chevilles.webp diff --git a/images/upper-blade-guide-1.JPEG b/images/upper-blade-guide-1.JPEG Binary files differnew file mode 100755 index 0000000..748f248 --- /dev/null +++ b/images/upper-blade-guide-1.JPEG diff --git a/images/upper-blade-guide-1.webp b/images/upper-blade-guide-1.webp Binary files differnew file mode 100755 index 0000000..4cd4f9b --- /dev/null +++ b/images/upper-blade-guide-1.webp diff --git a/images/w3m-display-options.png b/images/w3m-display-options.png Binary files differnew file mode 100755 index 0000000..0f1b7cb --- /dev/null +++ b/images/w3m-display-options.png diff --git a/images/windowmaker096.png b/images/windowmaker096.png Binary files differnew file mode 100755 index 0000000..f7832bc --- /dev/null +++ b/images/windowmaker096.png diff --git a/images/workshop-update.JPEG b/images/workshop-update.JPEG Binary files differnew file mode 100755 index 0000000..4c1385d --- /dev/null +++ b/images/workshop-update.JPEG diff --git a/images/workshop-update.webp b/images/workshop-update.webp Binary files differnew file mode 100755 index 0000000..8a68501 --- /dev/null +++ b/images/workshop-update.webp diff --git a/images/wttr.JPEG b/images/wttr.JPEG Binary files differnew file mode 100755 index 0000000..b5023f3 --- /dev/null +++ b/images/wttr.JPEG diff --git a/images/wttr.webp b/images/wttr.webp Binary files differnew file mode 100755 index 0000000..93bf3d2 --- /dev/null +++ b/images/wttr.webp diff --git a/images/x200.JPEG b/images/x200.JPEG Binary files differnew file mode 100755 index 0000000..401db0f --- /dev/null +++ b/images/x200.JPEG diff --git a/images/x200.webp b/images/x200.webp Binary files differnew file mode 100755 index 0000000..98863ac --- /dev/null +++ b/images/x200.webp diff --git a/images/xterm-images-400.jpg b/images/xterm-images-400.jpg Binary files differnew file mode 100755 index 0000000..238017a --- /dev/null +++ b/images/xterm-images-400.jpg diff --git a/images/zellij.JPEG b/images/zellij.JPEG Binary files differnew file mode 100755 index 0000000..e58c0d3 --- /dev/null +++ b/images/zellij.JPEG diff --git a/images/zellij.webp b/images/zellij.webp Binary files differnew file mode 100755 index 0000000..444f32f --- /dev/null +++ b/images/zellij.webp diff --git a/index.php b/index.php new file mode 100755 index 0000000..433c0ee --- /dev/null +++ b/index.php @@ -0,0 +1,149 @@ +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <meta property="og:image" content="https://wittamore.fr/images/bloggings.jpg"> + <meta property="og:image:type" content="image/jpg"> + <meta property="og:image:width" content="1024"> + <meta property="og:image:height" content="640"> + <title>Bloggings</title> +</head> +<body> + <header> + <h1>Bloggings</h1> + <nav> + <p> + <b>Blog</b> | <a + href="/static/index.php">Static</a> | <a + href="/search.php">Search</a> | <a + href="https://git.wittamore.fr" target="_blank" title="Git">Git</a> | <a + href="https://social.wittamore.fr/philip" target="_blank" title="Fediverse">Fedi</a> | <a + href="/feeds/index.php" target="_blank" title="Feeds">Feeds</a> | <a + href="/rss.xml">RSS</a> + </p> + </nav> + <div class="banner"> + <img src="/images/me.jpg" alt="logo"> + <p>Comments: <a href="https://social.wittamore.fr/philip">@philip</a><br> + Contact: <a href="/images/deltachatqr.jpg">Deltachat</a> + </p> + </div> + </header> + <main> +<!-- list start --> +<?php +// build reverse article subdirs (years) +$years = []; +$revyears = []; +$articlesubdir = new DirectoryIterator("./articles/"); +foreach ($articlesubdir as $dirinfo) { + if ($dirinfo->isDir() && !$dirinfo->isDot()) { + $years[] = $dirinfo->getFilename(); + } +} +rsort($years); +// iterate through years +if (is_array($years)) { + foreach ($years as $key => $year) { + $items = []; + $thisdir = new DirectoryIterator("./articles/" . $year); + // iterate through files + foreach ($thisdir as $file) { + if (!$file->isDot()) { + $flag = ""; + $articledate = date("Y-m-d"); + $filemodified = date( + "Y-m-d", + filemtime("./articles/" . $year . "/" . $file), + ); + $content = file_get_contents($file->getPathname()); + # get first h3 + preg_match("/<h3>(.*?)<\/h3>/s", $content, $date); + if (isset($date[1]) && strtotime($date[1])) { + $articledate = date("Y-m-d", strtotime($date[1])); + } + if ( + $articledate >= + date("Y-m-d", strtotime(date("Y-m-d") . " - 2 days")) + ) { + $flag = "<small><i> New</i></small>"; + } + preg_match("/<h2>(.*?)<\/h2>/s", $content, $titre); + $title = "?"; + if (isset($titre[1])) { + $title = ucfirst($titre[1]); + } + + $items[] = + '<li class="articlelink">' . + substr($articledate, -5) . + ' <a href="' . + htmlentities($file->getPathname()) . + '">' . + $title . "</a>" . + $flag . + "</li>\n"; + } + } + + rsort($items); + + echo "<article>\n"; + echo "<h2 class=\"blogyear\">" . $year . "</h2>\n"; + + foreach ($items as $key => $val) { + if ($key == 0) { + if ($year == date("Y")) { + preg_match("'href=\"(.*?)\"'si", $val, $match); + if ($match) { + $content = file_get_contents($match[1], false, null, 1); + preg_match( + "'\<article\>(.*?)<\/article\>'si", + $content, + $article, + ); + if ($article) { + # print latest article contents + echo $article[1] . "\n<p> </p>\n"; + } + } + } + echo "<ul>\n"; + } + # print link to article + echo $val; + } + echo "</ul>\n"; + echo "</article>\n"; + } +} else { + echo "Not an array"; +} +?> +<!-- list end --> + </main> + <footer> + <p><a href="/static/Mention-Legal.html">GDPR notice</a></p> + </footer> +</body> +</html> + + + + + + + + + + + + + + + + + diff --git a/logo.png b/logo.png Binary files differnew file mode 100644 index 0000000..3aee8e7 --- /dev/null +++ b/logo.png diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..6f538e6 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,70 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; +error_log /var/log/nginx/error.log; +include /etc/nginx/modules-enabled/*.conf; +worker_rlimit_nofile 2048; + + +events { + worker_connections 768; + # multi_accept on; +} + +http { + + ## + # Basic Settings + ## + + # Caching configuration for snac + proxy_cache_path /var/cache/nginx/snac_cache levels=1:2 keys_zone=snac:10m max_size=1g inactive=1440m use_temp_path=off; + # request limiting + limit_req_zone $binary_remote_addr zone=mylimit:10m rate=50r/s; + + sendfile on; + tcp_nopush on; + types_hash_max_size 2048; + # server_tokens off; + + # server_names_hash_bucket_size 64; + # server_name_in_redirect off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ## + # SSL Settings + ## + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE + ssl_prefer_server_ciphers on; + + ## + # Logging Settings + ## + + access_log /var/log/nginx/access.log; + + ## + # Gzip Settings + ## + + gzip on; + + # gzip_vary on; + # gzip_proxied any; + # gzip_comp_level 6; + # gzip_buffers 16 8k; + # gzip_http_version 1.1; + # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + ## + # Virtual Host Configs + ## + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} + + diff --git a/robots.txt b/robots.txt new file mode 100755 index 0000000..8f6cf3b --- /dev/null +++ b/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Disallow: +Sitemap: https://wittamore.fr/sitemap.xml @@ -0,0 +1,850 @@ +<?xml version='1.0' encoding='UTF-8' ?> +<rss version='2.0' xmlns:atom='http://www.w3.org/2005/Atom'> +<channel> +<title>Bloggings</title> +<link>https://wittamore.fr</link> +<description>Ramblings from Brittany</description> +<lastBuildDate>2026-07-02T10:22:43,069276386+02:00</lastBuildDate> +<atom:link href='https://wittamore.fr/rss.xml' rel='self' type='application/rss+xml' /> + +<item> + <title>upgrade to dwm 6.8</title> + <pubDate>2026-06-29</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/upgrade-to-dwm-6.8.html</link> + <guid>https://wittamore.fr/articles/2026/upgrade-to-dwm-6.8.html</guid> + <category> </category> + <description><![CDATA[ <h4>dwm with some patches</h4> + <p>I've been using dwm 6.5 for a while now, so I thought it + was time to upgrade to dwm 6.8.</p> + <p>Ideally I'd settle for a horizontal scrolling wm as my + Thinkpad screen is small, but after flirting with nwm I'm + staying with dwm for now.</p> + <p>I've added a few patches and shortcuts that make it easy + for me to circulate between windows and workspaces. I haven't + added the xrdb patch this time, instead I added the colorbar + patch which nicely colours the dwmblocks status bar and keeps + things simple. Many of the tiling patches I added to 6.5 have + been left out as I just don't use them. On a small 1366x768 + screen splitting it renders it useless so instead I use the + monocle layout by default and switch workspaces with + super+right and left, and switch windows with super+up and + down.</p> + <p><a href= + "https://git.wittamore.fr/dwm-6.8-patched/tree">It's up on my + git server if you are interested.</a></p>]]></description> +</item> +<item> + <title>Freedos thoughts</title> + <pubDate>2026-06-11</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Freedos-thoughts.html</link> + <guid>https://wittamore.fr/articles/2026/Freedos-thoughts.html</guid> + <category> </category> + <description><![CDATA[ <h4>Maybe I have the best system already</h4> + <p> + I've been looking at Freedos for the retrocomputing nostalgia value + and the text-only goodness. + </p> + <p> + I wound up using Qemu on my X270 as Freedos on bare metal means my 14 + year old Thinkpad X220 is way too modern. + </p> + <p> + Yes there's some crazy good software, and it's fun. But using + fullscreen in Qemu means a fuzzy display, and I came to measure how + the terminal on Linux is truly light years ahead. + </p> + <p> + In fact it pushes home that my Artix / DWM / Kitty setup is the + veritable retrocomputing bees knees + </p>]]></description> +</item> +<item> + <title>semware-tse-editor</title> + <pubDate>2026-06-13</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/semware-tse-editor.html</link> + <guid>https://wittamore.fr/articles/2026/semware-tse-editor.html</guid> + <category> </category> + <description><![CDATA[ <h4>A very interesting editor for DOS, Linux and Windows</h4> + <p> + <img src="/images/blog/semware.png" alt="semware editor screenshot" /> + </p> + <p> + I stumbled upon the #Semware editor (TSE) for #Linux, Windows and #Dos + and thought I'd share it here. Originally named Qedit in 1985, the + latest version is dated May 2026. I don't know about Windows (as I + haven't used it in years), but the Linux version is a pleasant CLI app + with many features, including it's own application language. The more + I look at it, the more I realise that it is a profound piece of + software. + </p> + <p> + <a href="https://www.semware.com/">www.semware.com</a><br /> + <a href="https://www.youtube.com/@knudvaneeden" + >Knud Vaneeded YT videos</a + > + </p>]]></description> +</item> +<item> + <title>Living on Snac's bleeding edge</title> + <pubDate>2026-05-25</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Living-on-Snac's-bleeding-edge.html</link> + <guid>https://wittamore.fr/articles/2026/Living-on-Snac's-bleeding-edge.html</guid> + <category> </category> + <description><![CDATA[ <h4>Autoupdate script</h4> + <p> + I like living on the bleeding edge of + <a href="https://codeberg.org/grunfink/snac2">snac2</a>, so I added + this (banal) script to /etc/cron.daily. + </p> + <pre> +#!/usr/bin/env bash +# snaceto +# Social Networks Are Crap Except This One + +cd /home/snac/src/snac2 || exit + +sudo -u snac git remote update +UPDATED="$(git status -uno | grep -c 'up to date')" + +if [ "$UPDATED" -ne "1" ] ; then + echo "Updating..." + systemctl stop snac.service + # backup data before update + TODAY=$(date "+%Y-%m-%d-%H-%m") + BACKUPDIR="/opt/backups" + mkdir -p "$BACKUPDIR" + FILENAME="$BACKUPDIR/pre-update-snac-$TODAY.tar.gz" + tar -zcf "$FILENAME" /home/snac/data + # pull latest + sudo -u snac git pull + sudo -u snac make + make install + sudo -u snac snac upgrade /home/snac/data + systemctl start snac.service + VERSION="$(snac | head -n2)" + toot post "My bleeding edge #snac2 server has been updated<br>$VERSION" +fi +</pre>]]></description> +</item> +<item> + <title>Web sites that work well in Dillo</title> + <pubDate>2026-06-19</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Web-sites-that-work-well-in-Dillo.html</link> + <guid>https://wittamore.fr/articles/2026/Web-sites-that-work-well-in-Dillo.html</guid> + <category> </category> + <description><![CDATA[ <h4>and other minimal browsers</h4> + <p> + I'm always looking for sane web sites. By sane I mean websites that + are not bloated with useless eye candy, nor have any ketamin fired + javascript. Sites that just want to display data in the form of easily + readable text with or without some images. + </p> + <p> + There is an increasing number of web sites that do this, as it's a + normal backlash to the slop infested, obfuscated tar pit that the + internet has sadly become. I haven't found a single compendium of + sites, but I'll list some here. + </p> + <p> + One of the things I find realy interesting are web proxies that render + sites into a saner version, like + <a href="https://www.textise.net/">Textise</a> or + <a href="https://txt.dc09.xyz/">Textdot</a>. I fact I'm wondering if I + could roll my own simple version using php and rdrview. + </p> + <p> + This list incomplete list will be updated as I find them, or maybe you + dear reader might have suggestions? + </p> + <p> + <a href="https://dir.dillo-browser.org/" + >Directory of useful sites that work reasonably well on Dillo</a + > + </p> + <p><a href="https://wiby.me/">Wiby search engine</a></p> + <p> + <a href="https://ronaldsvilcins.com/2021/09/15/text-only-websites/" + >Text only web sites</a + > + </p>]]></description> +</item> +<item> + <title>Ghostbsd installation</title> + <pubDate>2026-06-14</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Ghostbsd-installation.html</link> + <guid>https://wittamore.fr/articles/2026/Ghostbsd-installation.html</guid> + <category> </category> + <description><![CDATA[ <h4>messing around on a sunday</h4> + <p> + So now we have Microsoft delivering Linux, and the Arch based AUR + getting wrecked by people who should be locked into a public pillory, + and I have some free time. Well in fact a lot of free time now that + I'm retired. + </p> + <p> + So. I downloaded the latest #Ghostbsd Mate and cut a usb key, then + plugged it into my Thinkpad X220. + </p> + <p> + The installation was simple and uneventful, and it booted without + problem. Everything worked out of the box. Mate seems pretty and + unobfuscated, just like me. + </p> + <p>So far I've done just two things:</p> + <p> + (1) to get some thinkpad function keys to work I've added in + /boot/loader.conf + </p> + <pre> +acpi_ibm_load="YES" +</pre + > + <p> + Printscreen works, brightness and volume controls work, vol mute + works, lid led works, but the MICMUTE button doesn't work.<br /> + For the X220 MICMUTE key, which is F20 or 0xBE (190), unfortunately + Ghostbsd's acpi_ibm doesn't pass on the event, so any script for it in + /etc/devd won't be fired. Checking the source code, acpi_ibm only accepts + a subset of events accepted by Linux's thinkpad_acpi, which appears to + cover them all. It's unfortunate as I'm a bit pedantic about keys but I + guess I'll have to live with it as the code is 20 years old and I + doubt it will change anytime soon. + </p> + + <p>FYI, acpi_ibm accepts only these keyboard events:</p> + <PRE> + LCD_BACKLIGHT + SUSPEND_TO_RAM + BLUETOOTH + SCREEN_EXPAND + SUSPEND_TO_DISK + BRIGHTNESS_UP + BRIGHTNESS_DOWN + THINKLIGHT + ZOOM + VOLUME_UP + VOLUME_DOWN + MUTE + ACCESS_IBM_BUTTON + </pre> + <p>(2) to enable the automounting of usb storage media:</p> + <pre> +sudo pkg install automount +</pre + > + <p> + I've replaced the default Firefox with Librewolf, and I'm not too sure + about zsh instead of bash, and why not doas instead of sudo? + </p> + <p> + Ghostbsd is a very nice BSD distribution that's been around since + 2010. + </p> + <p><a href="https://www.ghostbsd.org/">https://www.ghostbsd.org</a></p>]]></description> +</item> +<item> + <title>displaying MATE on a 1366x768 screen</title> + <pubDate>2026-06-15</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/displaying-MATE-on-a-1366x768-screen.html</link> + <guid>https://wittamore.fr/articles/2026/displaying-MATE-on-a-1366x768-screen.html</guid> + <category> </category> + <description><![CDATA[ <h4>xrandr to the rescue</h4> + <p> + Unfortunately several Ghostbsd MATE configuration windows are taller + than my Thinkpad X220's 768 pixels. I made both the top and bottom + panels autohide, but a better solution is to use xrandr: + </p> + <pre>sudo pkg install xrandr</pre> + <p> + type xrandr to get the name of your display, in my case LVDS-1 on my + Thinkpad X220. + </p> + <p>To change 16:9 screen resolution, you can type:</p> + <pre> +xrandr --output LVDS-1 --panning 1600x900 --scale 1.171x1.172</pre + > + <p>or</p> + <pre> +xrandr --output LVDS-1 --panning 1920x1080 --scale 1.406x1.406</pre + > + <p>To get back to the original resolution type:</p> + <pre>xrandr --output LVDS-1 --panning 1366x768 --scale 1x1</pre> + <p> + Here's a simple bash script that will rotate the screen resolution on + each call. I've added a MATE keyboard shortcut (CTRL+RIGHT) to run + this script. It is intended for my 1366x768 screen, so it will need + adapting for other screens. + </p> + <pre> +#!/usr/bin/env bash + +RES="$(xdpyinfo | grep dimensions | sed -r 's/^[^0-9]*([0-9]+x[0-9]+).*$/\1/')" +# xdpy results can vary by 1 or two pixels +# so take the first two characters +RES="${RES:0:2}" + +case "$RES" in + "13") + xrandr --output LVDS-1 --panning 1600x900 --scale 1.171x1.172 + ;; + "16") + xrandr --output LVDS-1 --panning 1920x1080 --scale 1.406x1.406 + ;; + *) + xrandr --output LVDS-1 --panning 1366x768 --scale 1x1 + ;; +esac + </pre> + <p>I doubt you can do that in Wayland.</p>]]></description> +</item> +<item> + <title>Oops! I did it again</title> + <pubDate>2026-05-31</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Oops!-I-did-it-again.html</link> + <guid>https://wittamore.fr/articles/2026/Oops!-I-did-it-again.html</guid> + <category> </category> + <description><![CDATA[ <h4>More blog improvements</h4> + <p> + This blog now + <a href="https://git.wittamore.fr/bloggings/tree/" + >sits in my git repository</a + >, so I made a few changes. + </p> + <p> + One is the + <a href="https://git.wittamore.fr/blog-scripts/tree/blogsend" + >blogsend</a + > + bash script which now does a git commit on my local machine, and a git + pull on the remote web server. The other is the git repos itself which + now has the web site in one repos, and the bash scripts in another. + </p> + <p> + All this is probably of little interest to anyone other than myself, + but if you want to run a smol blog you might find it useful, after all + that's why I'm posting it. + </p>]]></description> +</item> +<item> + <title>smolweb validation in Dillo</title> + <pubDate>2026-06-22 +.dillo/actions/smolcheck.sh</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/smolweb-validation-in-Dillo.html</link> + <guid>https://wittamore.fr/articles/2026/smolweb-validation-in-Dillo.html</guid> + <category> </category> + <description><![CDATA[ <h4>a simple page_action script</h4> + <p> + I added smolweb-validator as a page action in Dillo. It's a bit of a + pain as the validator is a java application. The action script is + brutal so far but it works. + </p> + <pre><code>#!/bin/sh + +#smolweb validator location +smp="$HOME/src/smolweb-validator" + +# validate +cd $smp +output="$(java -cp "jsoup-1.21.2.jar:." SmolwebValidator $url --verbose)" +echo "$output" | dilloc rawload +</code></pre> + <p> + The action is added to ~/.dillo/dillorc and appears in Dillo's + right-click menu + </p> + <pre> +page_action="Smolcheck:~/.dillo/actions/smolcheck.sh" +</pre + > + <p> + see + <a href="https://codeberg.org/smolweb/smolweb-validator" + >https://codeberg.org/smolweb/smolweb-validator</a + > + </p> + <p> </p>]]></description> +</item> +<item> + <title>Updated blogthis script</title> + <pubDate>2026-06-16</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Updated-blogthis-script.html</link> + <guid>https://wittamore.fr/articles/2026/Updated-blogthis-script.html</guid> + <category> </category> + <description><![CDATA[ <h4>now with markdown</h4> + <p> + I've updated my blogthis script so I can write articles in markdown. + </p> + <p> + It saves me having to write html in the articles and avoids errors. + </p> + <p> + I've uploaded it to + <a href="https://git.wittamore.fr/blog-scripts/tree/" + >my git server</a + > + </p>]]></description> +</item> +<item> + <title>Hebdogiciel</title> + <pubDate>2026-06-07</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Hebdogiciel.html</link> + <guid>https://wittamore.fr/articles/2026/Hebdogiciel.html</guid> + <category> </category> + <description><![CDATA[ <h4>Flashback to the 80's</h4> + <p> + After my ZX81 I purchased an Amstrad CPC464, and oh joy I discovered + Hebdogiciel. Before the internet this magazine printed the code for + games and programs that you could painfully copy out. Today I + discovered that there's an online archive: + </p> + + <p> + <a + href="https://www.abandonware-magazines.org/affiche_mag.php?mag=7&page=1" + >abandonware-magazines</a + > + </p> + + <p> + <b>1983–1987:</b> Imagine a weekly magazine that, in addition to + providing us with a weekly program to type out and delivering + uncompromising hardware and software reviews (and that’s saying + something), also featured TV listings, movie, music, and comic book + releases, assembly language tutorials, a comic strip, a discount club, + walkthroughs... A tone that blew you away from the first page to the + last, Carali’s drawings filling in the gaps, lawsuits galore… Simply + extraordinary, unmatched. + </p>]]></description> +</item> +<item> + <title>More on Dillo</title> + <pubDate>2026-06-21</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/More-on-Dillo.html</link> + <guid>https://wittamore.fr/articles/2026/More-on-Dillo.html</guid> + <category> </category> + <description><![CDATA[ <h4>update on yesterday's post</h4> + <p>The <a href="https://git.dillo-browser.org/dillo">git + version of dillo</a> has a new option: page_action, which + enables running rdrview on the current page if the + setting:</p> + <pre><code>page_action="Reader mode:dilloc dump | rdrview -H | dilloc load" +</code></pre> + <p>is set in dillorc.</p> + <p>When you right click on a page the menu will now include + the option "Reader Mode".</p>]]></description> +</item> +<item> + <title>Installation of Snac2 on Artix Linux</title> + <pubDate>2026-05-20 +add user + In /home/snac/data/server.json edit these settings: + As root, create the dinit script: /etc/dinit.d/snac2 +enable snac2 +NGINX Media caching +in /etc/nginx/nginx.conf http section add: +make cache folder +/etc/nginx/sites-available/example.com +add pleroma relay</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Installation-of-Snac2-on-Artix-Linux.html</link> + <guid>https://wittamore.fr/articles/2026/Installation-of-Snac2-on-Artix-Linux.html</guid> + <category> </category> + <description><![CDATA[<h4>Installation of snac2 on Artix Linux +Nginx +Dinit</h4> +<pre> +useradd -m -s /bin/bash snac +passwd snac +usermod -aG wheel snac +su - snac +mkdir src +cd src +git clone https://codeberg.org/grunfink/snac2.git +cd snac2 +make +sudo make install +snac init ~/data +</pre> +<pre> +Host name: your.server.domain +Path prefix: leave empty +Network address: 127.0.0.1 +Port: 8001 +</pre> +<pre> +snac adduser ~/data +</pre> +<pre> +admin_email +admin_account +title +short_description + +and add this line: + +"proxy_media: true +</pre> +<pre> +type = process +run-as = snac +command = /usr/local/bin/snac httpd /home/snac/data +depends-on = NetworkManager +restart = true +smooth-recovery = yes +log-type = file +logfile = /var/log/dinit/snac2.log +</pre> +<pre> +dinitcl enable snac2 +</pre> +<p> +see <a href="https://it-notes.dragas.net/2025/01/29/improving-snac-performance-with-nginx-proxy-cache/">it-notes.fragas.net</a> +</p> +<p> +see <a href="https://comam.es/snac-doc/snac.8.html">comam.es</a>.<br/> +Use $host not $http_host (gixy) +</p> +<pre> +proxy_cache_path /var/cache/nginx/snac_cache levels=1:2 keys_zone=snac:10m max_size=1g inactive=1440m use_temp_path=off; +</pre> +<pre> +mkdir -p /var/cache/nginx/snac_cache +</pre> +<pre> +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name example.com; + + ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/example.com/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + # openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + +location / { + proxy_pass http://127.0.0.1:8001; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $remote_addr; +} + +location ~ ^/.+/s/ { + proxy_cache snac; + proxy_pass http://127.0.0.1:8001; + proxy_set_header Host $host; + proxy_cache_valid 200 1d; + proxy_cache_valid 404 1h; + proxy_ignore_headers "Cache-Control" "Expires"; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; + proxy_cache_lock on; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Proxy-Cache $upstream_cache_status; +} +} + +server { + listen 80; + listen [::]:80; + server_name example.com; + return 301 https://$host$request_uri; +} +</pre> + +<p>see <a href="https://relaylist.com/">relaylist.com</a></p> +<pre> +su - snac +snac adduser ~/data relay +snac follow ~/data relay https://relay.toot.io/actor +</pre>]]></description> +</item> +<item> + <title>Funeral blues</title> + <pubDate>2026-05-07</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Funeral-blues.html</link> + <guid>https://wittamore.fr/articles/2026/Funeral-blues.html</guid> + <category> </category> + <description><![CDATA[ <h4>A poem by W.H.Auden (1907-1973)</h4> + <p>This poem was read in the film "Four marriages and a funeral"</p> + + <p>I just stumbled upon it and wanted to write it down.</p> + + <code> +Funeral Blues + +Stop all the clocks, cut off the telephone. +Prevent the dog from barking with a juicy bone, + +Silence the pianos and with muffled drum +Bring out the coffin, let the mourners come. + +Let aeroplanes circle moaning overhead +Scribbling in the sky the message He is Dead, + +Put crêpe bows round the white necks of the public doves, +Let the traffic policemen wear black cotton gloves. + +He was my North, my South, my East and West, +My working week and my Sunday rest + +My noon, my midnight, my talk, my song; +I thought that love would last forever, I was wrong. + +The stars are not wanted now; put out every one, +Pack up the moon and dismantle the sun. + +Pour away the ocean and sweep up the wood; +For nothing now can ever come to any good. + </code>]]></description> +</item> +<item> + <title>From minimalist to smolweb</title> + <pubDate>2026-05-06</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/From-minimalist-to-smolweb.html</link> + <guid>https://wittamore.fr/articles/2026/From-minimalist-to-smolweb.html</guid> + <category> </category> + <description><![CDATA[ <h4>When smarter people get things done</h4> + <p> + In 2023 + about what I was feeling at the time on the sorry state of the web. + </p> + <p> + I'm happy to say that the people at + <a href="https://smolweb.org">smolweb.org</a> have made a far better + statement than mine. + </p> + <p> + As this blog is now a few years old, I'll be working on making it pass + the + <a href="https://smolweb.org/validator/">smolweb validation test</a>. + </p>]]></description> +</item> +<item> + <title>De Gaulle - Two films</title> + <pubDate>2026-06-04</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/De-Gaulle---Two-films.html</link> + <guid>https://wittamore.fr/articles/2026/De-Gaulle---Two-films.html</guid> + <category> </category> + <description><![CDATA[ <h4>De Gaulle is more popular than ever</h4> + <p> + People who choose a country to be their home are often patriotic as + it's their choice, not mere circumstance. + </p> + <p>Personally I'm patriotic but certainly not nationalist.</p> + <blockquote> + <i + >“The difference between patriotism and nationalism is that the + patriot is proud of his country for what it does, and the + nationalist is proud of his country no matter what it does; the + first attitude creates a feeling of responsibility, but the second a + feeling of blind arrogance that leads to war.”</i + > + ― + <a href="https://en.wikipedia.org/wiki/Sydney_J._Harris" + >Sydney J. Harris</a + > + </blockquote> + <p></p> + <div class="wrapper"> + <iframe + src="https://www.youtube.com/embed/RTNzSKr3Hsw" + title="De Gaulle (2026) Official Trailer" + frameborder="0" + allow=" + accelerometer; + autoplay; + clipboard-write; + encrypted-media; + gyroscope; + picture-in-picture; + web-share; + " + referrerpolicy="strict-origin-when-cross-origin" + allowfullscreen + ></iframe> + </div>]]></description> +</item> +<item> + <title>Ladybird turns into a squashed bug</title> + <pubDate>2026-06-06</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Ladybird-turns-into-a-squashed-bug.html</link> + <guid>https://wittamore.fr/articles/2026/Ladybird-turns-into-a-squashed-bug.html</guid> + <category> </category> + <description><![CDATA[ <h4>Sponsored software hype</h4> + <p> + Last year + <a href="https://wittamore.fr/articles/2025/Ladybird-web-browser.html" + >I had high hopes for Ladybird</a + >, a web browser with a brand new engine written, from scratch and + open source. + </p> + <p> + Unfortunately + <a href="https://ladybird.org/posts/changing-how-we-develop-ladybird/" + >the code has now been locked</a + >, and the code was moved from Swift to Rust because ... AI code. + </p> + <p> + --> + <a href="https://exquisite.social/@thomholwerda/116697078838020135" + >The fash browser developed by "AI" hypeboys</a + > + </p>]]></description> +</item> +<item> + <title>My own private search engine</title> + <pubDate>2026-06-02</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/My-own-private-search-engine.html</link> + <guid>https://wittamore.fr/articles/2026/My-own-private-search-engine.html</guid> + <category> </category> + <description><![CDATA[ <h4>Messing around with a bad back in stormy weather</h4> + <p> + I made a bad movement in the shower this morning and made my back + scream again, so no woodworking today. One of the benefits of + retirement is that I get to choose the days activities. + </p> + <p> + I had installed + <a href="https://github.com/searxng/searxng">SearxNG</a> previously + more than two years ago, but as I'm constantly fiddling it wound up in + the bin. + </p> + <p> + Convalescing in the armchair I saw that my current vps is mostly idle + running just snac2, my blog, xmpp, gophernicus and other bits, so I + reinstalled SearxNG. + </p> + <p> + It's fast, responsive, and private. I have it behind an Nginx reverse + proxy, and only accepting my own IP address for now. + </p> + <p> + Another thing I did was to play around with the + <a href="https://getfresh.dev/">Fresh editor</a>. Like Micro, it can + be configured to use prettier, the code formatter. Ok, it's written in + Rust, but it is a nice editor with a retro computing vibe. It won't + replace Micro as my daily editor, but if I ever need a TUI IDE it + could be a good choice. + </p>]]></description> +</item> +<item> + <title>Filtering the web</title> + <pubDate>2026-06-20</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Filtering-the-web.html</link> + <guid>https://wittamore.fr/articles/2026/Filtering-the-web.html</guid> + <category> </category> + <description><![CDATA[ <h4>A browser that says no</h4> + <p> + To me the problem with the smolweb is that there's isn't a browser + that applies these rules. Dillo comes close, even though it needs a + makeover. + </p> + <p> + Dillo also has an rdrview plugin, but you have to manually write + rdrview: in front of the url in the url bar of every page to see the + results. + </p> + <p> + A better solution would be a smolweb plugin that filters the entire + html page rejecting anything that doesn't + <a href="https://smolweb.org/validator/">validate</a> or pass to + rdrview . Degrees of acceptance could be set in settings. + </p> + <p> + A dedicated browser wouldn't require searching for smolweb / smallweb + / indieweb blogs, sites, or articles. (There seems to be no such thing + as a smolweb search engine, although points for wiby, marginalia, kagi + and mojeek). + </p> + <p> + Just point the browser to any web site and if it borks, display the + "errors" or ignore it with a + <a + href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/418" + > + 418 I'm a teapot error</a + >: after all it's the web site author's responsibility to provide + clean, readable web sites. + </p> + <p> + Imagine a world where this plugin would be as ubiquitous as + ublock-origin, it could even change web site development. + </p>]]></description> +</item> +<item> + <title>Updated poem</title> + <pubDate>2026-06-01</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/Updated-poem.html</link> + <guid>https://wittamore.fr/articles/2026/Updated-poem.html</guid> + <category> </category> + <description><![CDATA[ <h4>Existance is the prize itself</h4> + <p> + Sadly a childhood friend of one of my nieces recently died at 36 of a + brain tumour. I gave this poem a re-write. + </p> + <pre> +Life is but a long farewell +While clawing at eternity's shell +No rhyme nor reason, no justice in sight +A frantic push against entropy's might +This fleeting hare +Brings grave dispair +But pointless is a daily dread +Of closing circuit in our bed +Here then gone, a spark in the night? +Our passing through is never trite +Best keep your malaise on the shelf +Existance is the prize itself +</pre + >]]></description> +</item> +<item> + <title>This blog wasn't so bad after all</title> + <pubDate>2026-05-05</pubDate> + <author>Philip A. Wittamore</author> + <link>https://wittamore.fr/articles/2026/This-blog-wasn't-so-bad-after-all.html</link> + <guid>https://wittamore.fr/articles/2026/This-blog-wasn't-so-bad-after-all.html</guid> + <category> </category> + <description><![CDATA[ <h4>Smolweb, Fediverse and blogs</h4> + <p> + For several months I've been using Gopher exclusively, I find it's + minimalist raw text files appealing. + </p> + <p> + It does have several drawbacks however. Recently I've been self + hosting my own fediverse instance at + <a href="https://social.wittamore.fr">social.wittamore.fr</a>. + </p> + <p> + Looking around for a fediverse blog, I realised that this html blog + wasn't so bad after all. + </p> + <p> + It's work in progress, as I would like to have it + <a href="https://smolweb.org/">smolweb validated</a>, and needs a + clean up. + </p>]]></description> +</item> + +</channel> +</rss> + diff --git a/sandbox/webproxy.php b/sandbox/webproxy.php new file mode 100644 index 0000000..977a890 --- /dev/null +++ b/sandbox/webproxy.php @@ -0,0 +1,7 @@ +<?php + +$output = shell_exec('curl https://wittamore.fr/articles/2026/Updated-blogthis-script.html'); + +echo $output; + +?> diff --git a/scripts/SimplePie.compiled.php b/scripts/SimplePie.compiled.php new file mode 100644 index 0000000..1eaddc1 --- /dev/null +++ b/scripts/SimplePie.compiled.php @@ -0,0 +1,20130 @@ +<?php +/** + * SimplePie + * + * A PHP-Based RSS and Atom Feed Framework. + * Takes the hard work out of managing a complete RSS/Atom solution. + * + * Please note: This file is automatically generated by a build script. The + * full original source is always available from http://simplepie.org/ + * + * Copyright (c) 2004-2023, Ryan Parman, Sam Sneddon, Ryan McCue, and contributors + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * * Neither the name of the SimplePie Team nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @link http://simplepie.org/ SimplePie + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +// SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue +// SPDX-License-Identifier: BSD-3-Clause + +namespace SimplePie { + +use InvalidArgumentException; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\UriFactoryInterface; +use Psr\SimpleCache\CacheInterface; +use SimplePie\Cache\Base; +use SimplePie\Cache\BaseDataCache; +use SimplePie\Cache\CallableNameFilter; +use SimplePie\Cache\DataCache; +use SimplePie\Cache\NameFilter; +use SimplePie\Cache\Psr16; +use SimplePie\Content\Type\Sniffer; +use SimplePie\Exception as SimplePieException; +use SimplePie\HTTP\Client; +use SimplePie\HTTP\ClientException; +use SimplePie\HTTP\FileClient; +use SimplePie\HTTP\Psr18Client; +use SimplePie\HTTP\Response; + +/** + * SimplePie + */ +class SimplePie +{ + /** + * SimplePie Name + */ + public const NAME = 'SimplePie'; + + /** + * SimplePie Version + */ + public const VERSION = '1.9.0'; + + /** + * SimplePie Website URL + */ + public const URL = 'http://simplepie.org'; + + /** + * SimplePie Linkback + */ + public const LINKBACK = '<a href="' . self::URL . '" title="' . self::NAME . ' ' . self::VERSION . '">' . self::NAME . '</a>'; + + /** + * No Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ + public const LOCATOR_NONE = 0; + + /** + * Feed Link Element Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ + public const LOCATOR_AUTODISCOVERY = 1; + + /** + * Local Feed Extension Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ + public const LOCATOR_LOCAL_EXTENSION = 2; + + /** + * Local Feed Body Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ + public const LOCATOR_LOCAL_BODY = 4; + + /** + * Remote Feed Extension Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ + public const LOCATOR_REMOTE_EXTENSION = 8; + + /** + * Remote Feed Body Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ + public const LOCATOR_REMOTE_BODY = 16; + + /** + * All Feed Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ + public const LOCATOR_ALL = 31; + + /** + * No known feed type + */ + public const TYPE_NONE = 0; + + /** + * RSS 0.90 + */ + public const TYPE_RSS_090 = 1; + + /** + * RSS 0.91 (Netscape) + */ + public const TYPE_RSS_091_NETSCAPE = 2; + + /** + * RSS 0.91 (Userland) + */ + public const TYPE_RSS_091_USERLAND = 4; + + /** + * RSS 0.91 (both Netscape and Userland) + */ + public const TYPE_RSS_091 = 6; + + /** + * RSS 0.92 + */ + public const TYPE_RSS_092 = 8; + + /** + * RSS 0.93 + */ + public const TYPE_RSS_093 = 16; + + /** + * RSS 0.94 + */ + public const TYPE_RSS_094 = 32; + + /** + * RSS 1.0 + */ + public const TYPE_RSS_10 = 64; + + /** + * RSS 2.0 + */ + public const TYPE_RSS_20 = 128; + + /** + * RDF-based RSS + */ + public const TYPE_RSS_RDF = 65; + + /** + * Non-RDF-based RSS (truly intended as syndication format) + */ + public const TYPE_RSS_SYNDICATION = 190; + + /** + * All RSS + */ + public const TYPE_RSS_ALL = 255; + + /** + * Atom 0.3 + */ + public const TYPE_ATOM_03 = 256; + + /** + * Atom 1.0 + */ + public const TYPE_ATOM_10 = 512; + + /** + * All Atom + */ + public const TYPE_ATOM_ALL = 768; + + /** + * All feed types + */ + public const TYPE_ALL = 1023; + + /** + * No construct + */ + public const CONSTRUCT_NONE = 0; + + /** + * Text construct + */ + public const CONSTRUCT_TEXT = 1; + + /** + * HTML construct + */ + public const CONSTRUCT_HTML = 2; + + /** + * XHTML construct + */ + public const CONSTRUCT_XHTML = 4; + + /** + * base64-encoded construct + */ + public const CONSTRUCT_BASE64 = 8; + + /** + * IRI construct + */ + public const CONSTRUCT_IRI = 16; + + /** + * A construct that might be HTML + */ + public const CONSTRUCT_MAYBE_HTML = 32; + + /** + * All constructs + */ + public const CONSTRUCT_ALL = 63; + + /** + * Don't change case + */ + public const SAME_CASE = 1; + + /** + * Change to lowercase + */ + public const LOWERCASE = 2; + + /** + * Change to uppercase + */ + public const UPPERCASE = 4; + + /** + * PCRE for HTML attributes + */ + public const PCRE_HTML_ATTRIBUTE = '((?:[\x09\x0A\x0B\x0C\x0D\x20]+[^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?)*)[\x09\x0A\x0B\x0C\x0D\x20]*'; + + /** + * PCRE for XML attributes + */ + public const PCRE_XML_ATTRIBUTE = '((?:\s+(?:(?:[^\s:]+:)?[^\s:]+)\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'))*)\s*'; + + /** + * XML Namespace + */ + public const NAMESPACE_XML = 'http://www.w3.org/XML/1998/namespace'; + + /** + * Atom 1.0 Namespace + */ + public const NAMESPACE_ATOM_10 = 'http://www.w3.org/2005/Atom'; + + /** + * Atom 0.3 Namespace + */ + public const NAMESPACE_ATOM_03 = 'http://purl.org/atom/ns#'; + + /** + * RDF Namespace + */ + public const NAMESPACE_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + + /** + * RSS 0.90 Namespace + */ + public const NAMESPACE_RSS_090 = 'http://my.netscape.com/rdf/simple/0.9/'; + + /** + * RSS 1.0 Namespace + */ + public const NAMESPACE_RSS_10 = 'http://purl.org/rss/1.0/'; + + /** + * RSS 1.0 Content Module Namespace + */ + public const NAMESPACE_RSS_10_MODULES_CONTENT = 'http://purl.org/rss/1.0/modules/content/'; + + /** + * RSS 2.0 Namespace + * (Stupid, I know, but I'm certain it will confuse people less with support.) + */ + public const NAMESPACE_RSS_20 = ''; + + /** + * DC 1.0 Namespace + */ + public const NAMESPACE_DC_10 = 'http://purl.org/dc/elements/1.0/'; + + /** + * DC 1.1 Namespace + */ + public const NAMESPACE_DC_11 = 'http://purl.org/dc/elements/1.1/'; + + /** + * W3C Basic Geo (WGS84 lat/long) Vocabulary Namespace + */ + public const NAMESPACE_W3C_BASIC_GEO = 'http://www.w3.org/2003/01/geo/wgs84_pos#'; + + /** + * GeoRSS Namespace + */ + public const NAMESPACE_GEORSS = 'http://www.georss.org/georss'; + + /** + * Media RSS Namespace + */ + public const NAMESPACE_MEDIARSS = 'http://search.yahoo.com/mrss/'; + + /** + * Wrong Media RSS Namespace. Caused by a long-standing typo in the spec. + */ + public const NAMESPACE_MEDIARSS_WRONG = 'http://search.yahoo.com/mrss'; + + /** + * Wrong Media RSS Namespace #2. New namespace introduced in Media RSS 1.5. + */ + public const NAMESPACE_MEDIARSS_WRONG2 = 'http://video.search.yahoo.com/mrss'; + + /** + * Wrong Media RSS Namespace #3. A possible typo of the Media RSS 1.5 namespace. + */ + public const NAMESPACE_MEDIARSS_WRONG3 = 'http://video.search.yahoo.com/mrss/'; + + /** + * Wrong Media RSS Namespace #4. New spec location after the RSS Advisory Board takes it over, but not a valid namespace. + */ + public const NAMESPACE_MEDIARSS_WRONG4 = 'http://www.rssboard.org/media-rss'; + + /** + * Wrong Media RSS Namespace #5. A possible typo of the RSS Advisory Board URL. + */ + public const NAMESPACE_MEDIARSS_WRONG5 = 'http://www.rssboard.org/media-rss/'; + + /** + * iTunes RSS Namespace + */ + public const NAMESPACE_ITUNES = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; + + /** + * XHTML Namespace + */ + public const NAMESPACE_XHTML = 'http://www.w3.org/1999/xhtml'; + + /** + * IANA Link Relations Registry + */ + public const IANA_LINK_RELATIONS_REGISTRY = 'http://www.iana.org/assignments/relation/'; + + /** + * No file source + */ + public const FILE_SOURCE_NONE = 0; + + /** + * Remote file source + */ + public const FILE_SOURCE_REMOTE = 1; + + /** + * Local file source + */ + public const FILE_SOURCE_LOCAL = 2; + + /** + * fsockopen() file source + */ + public const FILE_SOURCE_FSOCKOPEN = 4; + + /** + * cURL file source + */ + public const FILE_SOURCE_CURL = 8; + + /** + * file_get_contents() file source + */ + public const FILE_SOURCE_FILE_GET_CONTENTS = 16; + + /** + * @internal Default value of the HTTP Accept header when fetching/locating feeds + */ + public const DEFAULT_HTTP_ACCEPT_HEADER = 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1'; + + /** + * @var array<string, mixed> Raw data + * @access private + */ + public $data = []; + + /** + * @var string|string[]|null Error string (or array when multiple feeds are initialized) + * @access private + */ + public $error = null; + + /** + * @var int HTTP status code + * @see SimplePie::status_code() + * @access private + */ + public $status_code = 0; + + /** + * @var Sanitize instance of Sanitize class + * @see SimplePie::set_sanitize_class() + * @access private + */ + public $sanitize; + + /** + * @var string SimplePie Useragent + * @see SimplePie::set_useragent() + * @access private + */ + public $useragent = ''; + + /** + * @var string Feed URL + * @see SimplePie::set_feed_url() + * @access private + */ + public $feed_url; + + /** + * @var ?string Original feed URL, or new feed URL iff HTTP 301 Moved Permanently + * @see SimplePie::subscribe_url() + * @access private + */ + public $permanent_url = null; + + /** + * @var File Instance of File class to use as a feed + * @see SimplePie::set_file() + */ + private $file; + + /** + * @var string|false Raw feed data + * @see SimplePie::set_raw_data() + * @access private + */ + public $raw_data; + + /** + * @var int Timeout for fetching remote files + * @see SimplePie::set_timeout() + * @access private + */ + public $timeout = 10; + + /** + * @var array<int, mixed> Custom curl options + * @see SimplePie::set_curl_options() + * @access private + */ + public $curl_options = []; + + /** + * @var bool Forces fsockopen() to be used for remote files instead + * of cURL, even if a new enough version is installed + * @see SimplePie::force_fsockopen() + * @access private + */ + public $force_fsockopen = false; + + /** + * @var bool Force the given data/URL to be treated as a feed no matter what + * it appears like + * @see SimplePie::force_feed() + * @access private + */ + public $force_feed = false; + + /** + * @var bool Enable/Disable Caching + * @see SimplePie::enable_cache() + * @access private + */ + private $enable_cache = true; + + /** + * @var DataCache|null + * @see SimplePie::set_cache() + */ + private $cache = null; + + /** + * @var NameFilter + * @see SimplePie::set_cache_namefilter() + */ + private $cache_namefilter; + + /** + * @var bool Force SimplePie to fallback to expired cache, if enabled, + * when feed is unavailable. + * @see SimplePie::force_cache_fallback() + * @access private + */ + public $force_cache_fallback = false; + + /** + * @var int Cache duration (in seconds) + * @see SimplePie::set_cache_duration() + * @access private + */ + public $cache_duration = 3600; + + /** + * @var int Auto-discovery cache duration (in seconds) + * @see SimplePie::set_autodiscovery_cache_duration() + * @access private + */ + public $autodiscovery_cache_duration = 604800; // 7 Days. + + /** + * @var string Cache location (relative to executing script) + * @see SimplePie::set_cache_location() + * @access private + */ + public $cache_location = './cache'; + + /** + * @var string&(callable(string): string) Function that creates the cache filename + * @see SimplePie::set_cache_name_function() + * @access private + */ + public $cache_name_function = 'md5'; + + /** + * @var bool Reorder feed by date descending + * @see SimplePie::enable_order_by_date() + * @access private + */ + public $order_by_date = true; + + /** + * @var mixed Force input encoding to be set to the follow value + * (false, or anything type-cast to false, disables this feature) + * @see SimplePie::set_input_encoding() + * @access private + */ + public $input_encoding = false; + + /** + * @var int-mask-of<self::LOCATOR_*> Feed Autodiscovery Level + * @see SimplePie::set_autodiscovery_level() + * @access private + */ + public $autodiscovery = self::LOCATOR_ALL; + + /** + * Class registry object + * + * @var Registry + */ + public $registry; + + /** + * @var int Maximum number of feeds to check with autodiscovery + * @see SimplePie::set_max_checked_feeds() + * @access private + */ + public $max_checked_feeds = 10; + + /** + * @var array<Response>|null All the feeds found during the autodiscovery process + * @see SimplePie::get_all_discovered_feeds() + * @access private + */ + public $all_discovered_feeds = []; + + /** + * @var string Web-accessible path to the handler_image.php file. + * @see SimplePie::set_image_handler() + * @access private + */ + public $image_handler = ''; + + /** + * @var array<string> Stores the URLs when multiple feeds are being initialized. + * @see SimplePie::set_feed_url() + * @access private + */ + public $multifeed_url = []; + + /** + * @var array<int, static> Stores SimplePie objects when multiple feeds initialized. + * @access private + */ + public $multifeed_objects = []; + + /** + * @var array<mixed> Stores the get_object_vars() array for use with multifeeds. + * @see SimplePie::set_feed_url() + * @access private + */ + public $config_settings = null; + + /** + * @var int Stores the number of items to return per-feed with multifeeds. + * @see SimplePie::set_item_limit() + * @access private + */ + public $item_limit = 0; + + /** + * @var bool Stores if last-modified and/or etag headers were sent with the + * request when checking a feed. + */ + public $check_modified = false; + + /** + * @var array<string> Stores the default attributes to be stripped by strip_attributes(). + * @see SimplePie::strip_attributes() + * @access private + */ + public $strip_attributes = ['bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc']; + + /** + * @var array<string, array<string, string>> Stores the default attributes to add to different tags by add_attributes(). + * @see SimplePie::add_attributes() + * @access private + */ + public $add_attributes = ['audio' => ['preload' => 'none'], 'iframe' => ['sandbox' => 'allow-scripts allow-same-origin'], 'video' => ['preload' => 'none']]; + + /** + * @var array<string> Stores the default tags to be stripped by strip_htmltags(). + * @see SimplePie::strip_htmltags() + * @access private + */ + public $strip_htmltags = ['base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style']; + + /** + * @var string[]|string Stores the default attributes to be renamed by rename_attributes(). + * @see SimplePie::rename_attributes() + * @access private + */ + public $rename_attributes = []; + + /** + * @var bool Should we throw exceptions, or use the old-style error property? + * @access private + */ + public $enable_exceptions = false; + + /** + * @var Client|null + */ + private $http_client = null; + + /** @var bool Whether HTTP client has been injected */ + private $http_client_injected = false; + + /** + * The SimplePie class contains feed level data and options + * + * To use SimplePie, create the SimplePie object with no parameters. You can + * then set configuration options using the provided methods. After setting + * them, you must initialise the feed using $feed->init(). At that point the + * object's methods and properties will be available to you. + * + * Previously, it was possible to pass in the feed URL along with cache + * options directly into the constructor. This has been removed as of 1.3 as + * it caused a lot of confusion. + * + * @since 1.0 Preview Release + */ + public function __construct() + { + if (version_compare(PHP_VERSION, '7.2', '<')) { + exit('Please upgrade to PHP 7.2 or newer.'); + } + + $this->set_useragent(); + + $this->set_cache_namefilter(new CallableNameFilter($this->cache_name_function)); + + // Other objects, instances created here so we can set options on them + $this->sanitize = new Sanitize(); + $this->registry = new Registry(); + + if (func_num_args() > 0) { + trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_duration() directly.', \E_USER_DEPRECATED); + + $args = func_get_args(); + switch (count($args)) { + case 3: + $this->set_cache_duration($args[2]); + // no break + case 2: + $this->set_cache_location($args[1]); + // no break + case 1: + $this->set_feed_url($args[0]); + $this->init(); + } + } + } + + /** + * Used for converting object to a string + * @return string + */ + public function __toString() + { + return md5(serialize($this->data)); + } + + /** + * Remove items that link back to this before destroying this object + * @return void + */ + public function __destruct() + { + if (!gc_enabled()) { + if (!empty($this->data['items'])) { + foreach ($this->data['items'] as $item) { + $item->__destruct(); + } + unset($item, $this->data['items']); + } + if (!empty($this->data['ordered_items'])) { + foreach ($this->data['ordered_items'] as $item) { + $item->__destruct(); + } + unset($item, $this->data['ordered_items']); + } + } + } + + /** + * Force the given data/URL to be treated as a feed + * + * This tells SimplePie to ignore the content-type provided by the server. + * Be careful when using this option, as it will also disable autodiscovery. + * + * @since 1.1 + * @param bool $enable Force the given data/URL to be treated as a feed + * @return void + */ + public function force_feed(bool $enable = false) + { + $this->force_feed = $enable; + } + + /** + * Set the URL of the feed you want to parse + * + * This allows you to enter the URL of the feed you want to parse, or the + * website you want to try to use auto-discovery on. This takes priority + * over any set raw data. + * + * Deprecated since 1.9.0: You can set multiple feeds to mash together by passing an array instead + * of a string for the $url. Remember that with each additional feed comes + * additional processing and resources. + * + * @since 1.0 Preview Release + * @see set_raw_data() + * @param string|string[] $url This is the URL (or (deprecated) array of URLs) that you want to parse. + * @return void + */ + public function set_feed_url($url) + { + $this->multifeed_url = []; + if (is_array($url)) { + trigger_error('Fetching multiple feeds with single SimplePie instance is deprecated since SimplePie 1.9.0, create one SimplePie instance per feed and use SimplePie::merge_items to get a single list of items.', \E_USER_DEPRECATED); + foreach ($url as $value) { + $this->multifeed_url[] = $this->registry->call(Misc::class, 'fix_protocol', [$value, 1]); + } + } else { + $this->feed_url = $this->registry->call(Misc::class, 'fix_protocol', [$url, 1]); + $this->permanent_url = $this->feed_url; + } + } + + /** + * Set an instance of {@see File} to use as a feed + * + * @deprecated since SimplePie 1.9.0, use \SimplePie\SimplePie::set_http_client() or \SimplePie\SimplePie::set_raw_data() instead. + * + * @param File &$file + * @return bool True on success, false on failure + */ + public function set_file(File &$file) + { + // trigger_error(sprintf('SimplePie\SimplePie::set_file() is deprecated since SimplePie 1.9.0, please use "SimplePie\SimplePie::set_http_client()" or "SimplePie\SimplePie::set_raw_data()" instead.'), \E_USER_DEPRECATED); + + $this->feed_url = $file->get_final_requested_uri(); + $this->permanent_url = $this->feed_url; + $this->file = &$file; + + return true; + } + + /** + * Set the raw XML data to parse + * + * Allows you to use a string of RSS/Atom data instead of a remote feed. + * + * If you have a feed available as a string in PHP, you can tell SimplePie + * to parse that data string instead of a remote feed. Any set feed URL + * takes precedence. + * + * @since 1.0 Beta 3 + * @param string $data RSS or Atom data as a string. + * @see set_feed_url() + * @return void + */ + public function set_raw_data(string $data) + { + $this->raw_data = $data; + } + + /** + * Set a PSR-18 client and PSR-17 factories + * + * Allows you to use your own HTTP client implementations. + * This will become required with SimplePie 2.0.0. + */ + final public function set_http_client( + ClientInterface $http_client, + RequestFactoryInterface $request_factory, + UriFactoryInterface $uri_factory + ): void { + $this->http_client = new Psr18Client($http_client, $request_factory, $uri_factory); + } + + /** + * Set the default timeout for fetching remote feeds + * + * This allows you to change the maximum time the feed's server to respond + * and send the feed back. + * + * @since 1.0 Beta 3 + * @param int $timeout The maximum number of seconds to spend waiting to retrieve a feed. + * @return void + */ + public function set_timeout(int $timeout = 10) + { + if ($this->http_client_injected) { + throw new SimplePieException(sprintf( + 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure timeout in your HTTP client instead.', + __METHOD__, + self::class + )); + } + + $this->timeout = (int) $timeout; + + // Reset a possible existing FileClient, + // so a new client with the changed value will be created + if (is_object($this->http_client) && $this->http_client instanceof FileClient) { + $this->http_client = null; + } elseif (is_object($this->http_client)) { + // Trigger notice if a PSR-18 client was set + trigger_error(sprintf( + 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure the timeout in your HTTP client instead.', + __METHOD__, + get_class($this) + ), \E_USER_NOTICE); + } + } + + /** + * Set custom curl options + * + * This allows you to change default curl options + * + * @since 1.0 Beta 3 + * @param array<int, mixed> $curl_options Curl options to add to default settings + * @return void + */ + public function set_curl_options(array $curl_options = []) + { + if ($this->http_client_injected) { + throw new SimplePieException(sprintf( + 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure custom curl options in your HTTP client instead.', + __METHOD__, + self::class + )); + } + + $this->curl_options = $curl_options; + + // Reset a possible existing FileClient, + // so a new client with the changed value will be created + if (is_object($this->http_client) && $this->http_client instanceof FileClient) { + $this->http_client = null; + } elseif (is_object($this->http_client)) { + // Trigger notice if a PSR-18 client was set + trigger_error(sprintf( + 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure the curl options in your HTTP client instead.', + __METHOD__, + get_class($this) + ), \E_USER_NOTICE); + } + } + + /** + * Force SimplePie to use fsockopen() instead of cURL + * + * @since 1.0 Beta 3 + * @param bool $enable Force fsockopen() to be used + * @return void + */ + public function force_fsockopen(bool $enable = false) + { + if ($this->http_client_injected) { + throw new SimplePieException(sprintf( + 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure fsockopen in your HTTP client instead.', + __METHOD__, + self::class + )); + } + + $this->force_fsockopen = $enable; + + // Reset a possible existing FileClient, + // so a new client with the changed value will be created + if (is_object($this->http_client) && $this->http_client instanceof FileClient) { + $this->http_client = null; + } elseif (is_object($this->http_client)) { + // Trigger notice if a PSR-18 client was set + trigger_error(sprintf( + 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure fsockopen in your HTTP client instead.', + __METHOD__, + get_class($this) + ), \E_USER_NOTICE); + } + } + + /** + * Enable/disable caching in SimplePie. + * + * This option allows you to disable caching all-together in SimplePie. + * However, disabling the cache can lead to longer load times. + * + * @since 1.0 Preview Release + * @param bool $enable Enable caching + * @return void + */ + public function enable_cache(bool $enable = true) + { + $this->enable_cache = $enable; + } + + /** + * Set a PSR-16 implementation as cache + * + * @param CacheInterface $cache The PSR-16 cache implementation + * + * @return void + */ + public function set_cache(CacheInterface $cache) + { + $this->cache = new Psr16($cache); + } + + /** + * SimplePie to continue to fall back to expired cache, if enabled, when + * feed is unavailable. + * + * This tells SimplePie to ignore any file errors and fall back to cache + * instead. This only works if caching is enabled and cached content + * still exists. + * + * @deprecated since SimplePie 1.8.0, expired cache will not be used anymore. + * + * @param bool $enable Force use of cache on fail. + * @return void + */ + public function force_cache_fallback(bool $enable = false) + { + // @trigger_error(sprintf('SimplePie\SimplePie::force_cache_fallback() is deprecated since SimplePie 1.8.0, expired cache will not be used anymore.'), \E_USER_DEPRECATED); + $this->force_cache_fallback = $enable; + } + + /** + * Set the length of time (in seconds) that the contents of a feed will be + * cached + * + * @param int $seconds The feed content cache duration + * @return void + */ + public function set_cache_duration(int $seconds = 3600) + { + $this->cache_duration = $seconds; + } + + /** + * Set the length of time (in seconds) that the autodiscovered feed URL will + * be cached + * + * @param int $seconds The autodiscovered feed URL cache duration. + * @return void + */ + public function set_autodiscovery_cache_duration(int $seconds = 604800) + { + $this->autodiscovery_cache_duration = $seconds; + } + + /** + * Set the file system location where the cached files should be stored + * + * @deprecated since SimplePie 1.8.0, use SimplePie::set_cache() instead. + * + * @param string $location The file system location. + * @return void + */ + public function set_cache_location(string $location = './cache') + { + // @trigger_error(sprintf('SimplePie\SimplePie::set_cache_location() is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache()" instead.'), \E_USER_DEPRECATED); + $this->cache_location = $location; + } + + /** + * Return the filename (i.e. hash, without path and without extension) of the file to cache a given URL. + * + * @param string $url The URL of the feed to be cached. + * @return string A filename (i.e. hash, without path and without extension). + */ + public function get_cache_filename(string $url) + { + // Append custom parameters to the URL to avoid cache pollution in case of multiple calls with different parameters. + $url .= $this->force_feed ? '#force_feed' : ''; + $options = []; + if ($this->timeout != 10) { + $options[CURLOPT_TIMEOUT] = $this->timeout; + } + if ($this->useragent !== Misc::get_default_useragent()) { + $options[CURLOPT_USERAGENT] = $this->useragent; + } + if (!empty($this->curl_options)) { + foreach ($this->curl_options as $k => $v) { + $options[$k] = $v; + } + } + if (!empty($options)) { + ksort($options); + $url .= '#' . urlencode(var_export($options, true)); + } + + return $this->cache_namefilter->filter($url); + } + + /** + * Set whether feed items should be sorted into reverse chronological order + * + * @param bool $enable Sort as reverse chronological order. + * @return void + */ + public function enable_order_by_date(bool $enable = true) + { + $this->order_by_date = $enable; + } + + /** + * Set the character encoding used to parse the feed + * + * This overrides the encoding reported by the feed, however it will fall + * back to the normal encoding detection if the override fails + * + * @param string|false $encoding Character encoding + * @return void + */ + public function set_input_encoding($encoding = false) + { + if ($encoding) { + $this->input_encoding = (string) $encoding; + } else { + $this->input_encoding = false; + } + } + + /** + * Set how much feed autodiscovery to do + * + * @see self::LOCATOR_NONE + * @see self::LOCATOR_AUTODISCOVERY + * @see self::LOCATOR_LOCAL_EXTENSION + * @see self::LOCATOR_LOCAL_BODY + * @see self::LOCATOR_REMOTE_EXTENSION + * @see self::LOCATOR_REMOTE_BODY + * @see self::LOCATOR_ALL + * @param int-mask-of<self::LOCATOR_*> $level Feed Autodiscovery Level (level can be a combination of the above constants, see bitwise OR operator) + * @return void + */ + public function set_autodiscovery_level(int $level = self::LOCATOR_ALL) + { + $this->autodiscovery = $level; + } + + /** + * Get the class registry + * + * Use this to override SimplePie's default classes + * + * @return Registry + */ + public function &get_registry() + { + return $this->registry; + } + + /** + * Set which class SimplePie uses for caching + * + * @deprecated since SimplePie 1.3, use {@see set_cache()} instead + * + * @param class-string<Cache> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_cache_class(string $class = Cache::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::set_cache()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Cache::class, $class, true); + } + + /** + * Set which class SimplePie uses for auto-discovery + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Locator> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_locator_class(string $class = Locator::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Locator::class, $class, true); + } + + /** + * Set which class SimplePie uses for XML parsing + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Parser> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_parser_class(string $class = Parser::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Parser::class, $class, true); + } + + /** + * Set which class SimplePie uses for remote file fetching + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<File> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_file_class(string $class = File::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(File::class, $class, true); + } + + /** + * Set which class SimplePie uses for data sanitization + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Sanitize> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_sanitize_class(string $class = Sanitize::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Sanitize::class, $class, true); + } + + /** + * Set which class SimplePie uses for handling feed items + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Item> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_item_class(string $class = Item::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Item::class, $class, true); + } + + /** + * Set which class SimplePie uses for handling author data + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Author> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_author_class(string $class = Author::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Author::class, $class, true); + } + + /** + * Set which class SimplePie uses for handling category data + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Category> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_category_class(string $class = Category::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Category::class, $class, true); + } + + /** + * Set which class SimplePie uses for feed enclosures + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Enclosure> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_enclosure_class(string $class = Enclosure::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Enclosure::class, $class, true); + } + + /** + * Set which class SimplePie uses for `<media:text>` captions + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Caption> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_caption_class(string $class = Caption::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Caption::class, $class, true); + } + + /** + * Set which class SimplePie uses for `<media:copyright>` + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Copyright> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_copyright_class(string $class = Copyright::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Copyright::class, $class, true); + } + + /** + * Set which class SimplePie uses for `<media:credit>` + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Credit> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_credit_class(string $class = Credit::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Credit::class, $class, true); + } + + /** + * Set which class SimplePie uses for `<media:rating>` + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Rating> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_rating_class(string $class = Rating::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Rating::class, $class, true); + } + + /** + * Set which class SimplePie uses for `<media:restriction>` + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Restriction> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_restriction_class(string $class = Restriction::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Restriction::class, $class, true); + } + + /** + * Set which class SimplePie uses for content-type sniffing + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Sniffer> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_content_type_sniffer_class(string $class = Sniffer::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Sniffer::class, $class, true); + } + + /** + * Set which class SimplePie uses item sources + * + * @deprecated since SimplePie 1.3, use {@see get_registry()} instead + * + * @param class-string<Source> $class Name of custom class + * + * @return bool True on success, false otherwise + */ + public function set_source_class(string $class = Source::class) + { + trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->registry->register(Source::class, $class, true); + } + + /** + * Set the user agent string + * + * @param string $ua New user agent string. + * @return void + */ + public function set_useragent(?string $ua = null) + { + if ($this->http_client_injected) { + throw new SimplePieException(sprintf( + 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure user agent string in your HTTP client instead.', + __METHOD__, + self::class + )); + } + + if ($ua === null) { + $ua = Misc::get_default_useragent(); + } + + $this->useragent = (string) $ua; + + // Reset a possible existing FileClient, + // so a new client with the changed value will be created + if (is_object($this->http_client) && $this->http_client instanceof FileClient) { + $this->http_client = null; + } elseif (is_object($this->http_client)) { + // Trigger notice if a PSR-18 client was set + trigger_error(sprintf( + 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure the useragent in your HTTP client instead.', + __METHOD__, + get_class($this) + ), \E_USER_NOTICE); + } + } + + /** + * Set a namefilter to modify the cache filename with + * + * @param NameFilter $filter + * + * @return void + */ + public function set_cache_namefilter(NameFilter $filter): void + { + $this->cache_namefilter = $filter; + } + + /** + * Set callback function to create cache filename with + * + * @deprecated since SimplePie 1.8.0, use {@see set_cache_namefilter()} instead + * + * @param (string&(callable(string): string))|null $function Callback function + * @return void + */ + public function set_cache_name_function(?string $function = null) + { + // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache_namefilter()" instead.', __METHOD__), \E_USER_DEPRECATED); + + if ($function === null) { + $function = 'md5'; + } + + $this->cache_name_function = $function; + + $this->set_cache_namefilter(new CallableNameFilter($this->cache_name_function)); + } + + /** + * Set options to make SP as fast as possible + * + * Forgoes a substantial amount of data sanitization in favor of speed. This + * turns SimplePie into a dumb parser of feeds. + * + * @param bool $set Whether to set them or not + * @return void + */ + public function set_stupidly_fast(bool $set = false) + { + if ($set) { + $this->enable_order_by_date(false); + $this->remove_div(false); + $this->strip_comments(false); + $this->strip_htmltags([]); + $this->strip_attributes([]); + $this->add_attributes([]); + $this->set_image_handler(false); + $this->set_https_domains([]); + } + } + + /** + * Set maximum number of feeds to check with autodiscovery + * + * @param int $max Maximum number of feeds to check + * @return void + */ + public function set_max_checked_feeds(int $max = 10) + { + $this->max_checked_feeds = $max; + } + + /** + * @return void + */ + public function remove_div(bool $enable = true) + { + $this->sanitize->remove_div($enable); + } + + /** + * @param string[]|string|false $tags Set a list of tags to strip, or set empty string to use default tags, or false to strip nothing. + * @return void + */ + public function strip_htmltags($tags = '', ?bool $encode = null) + { + if ($tags === '') { + $tags = $this->strip_htmltags; + } + $this->sanitize->strip_htmltags($tags); + if ($encode !== null) { + $this->sanitize->encode_instead_of_strip($encode); + } + } + + /** + * @return void + */ + public function encode_instead_of_strip(bool $enable = true) + { + $this->sanitize->encode_instead_of_strip($enable); + } + + /** + * @param string[]|string $attribs + * @return void + */ + public function rename_attributes($attribs = '') + { + if ($attribs === '') { + $attribs = $this->rename_attributes; + } + $this->sanitize->rename_attributes($attribs); + } + + /** + * @param string[]|string $attribs + * @return void + */ + public function strip_attributes($attribs = '') + { + if ($attribs === '') { + $attribs = $this->strip_attributes; + } + $this->sanitize->strip_attributes($attribs); + } + + /** + * @param array<string, array<string, string>>|'' $attribs + * @return void + */ + public function add_attributes($attribs = '') + { + if ($attribs === '') { + $attribs = $this->add_attributes; + } + $this->sanitize->add_attributes($attribs); + } + + /** + * Set the output encoding + * + * Allows you to override SimplePie's output to match that of your webpage. + * This is useful for times when your webpages are not being served as + * UTF-8. This setting will be obeyed by {@see handle_content_type()}, and + * is similar to {@see set_input_encoding()}. + * + * It should be noted, however, that not all character encodings can support + * all characters. If your page is being served as ISO-8859-1 and you try + * to display a Japanese feed, you'll likely see garbled characters. + * Because of this, it is highly recommended to ensure that your webpages + * are served as UTF-8. + * + * The number of supported character encodings depends on whether your web + * host supports {@link http://php.net/mbstring mbstring}, + * {@link http://php.net/iconv iconv}, or both. See + * {@link http://simplepie.org/wiki/faq/Supported_Character_Encodings} for + * more information. + * + * @param string $encoding + * @return void + */ + public function set_output_encoding(string $encoding = 'UTF-8') + { + $this->sanitize->set_output_encoding($encoding); + } + + /** + * @return void + */ + public function strip_comments(bool $strip = false) + { + $this->sanitize->strip_comments($strip); + } + + /** + * Set element/attribute key/value pairs of HTML attributes + * containing URLs that need to be resolved relative to the feed + * + * Defaults to |a|@href, |area|@href, |blockquote|@cite, |del|@cite, + * |form|@action, |img|@longdesc, |img|@src, |input|@src, |ins|@cite, + * |q|@cite + * + * @since 1.0 + * @param array<string, string|string[]>|null $element_attribute Element/attribute key/value pairs, null for default + * @return void + */ + public function set_url_replacements(?array $element_attribute = null) + { + $this->sanitize->set_url_replacements($element_attribute); + } + + /** + * Set the list of domains for which to force HTTPS. + * @see Sanitize::set_https_domains() + * @param array<string> $domains List of HTTPS domains. Example array('biz', 'example.com', 'example.org', 'www.example.net'). + * @return void + */ + public function set_https_domains(array $domains = []) + { + $this->sanitize->set_https_domains($domains); + } + + /** + * Set the handler to enable the display of cached images. + * + * @param string|false $page Web-accessible path to the handler_image.php file. + * @param string $qs The query string that the value should be passed to. + * @return void + */ + public function set_image_handler($page = false, string $qs = 'i') + { + if ($page !== false) { + $this->sanitize->set_image_handler($page . '?' . $qs . '='); + } else { + $this->image_handler = ''; + } + } + + /** + * Set the limit for items returned per-feed with multifeeds + * + * @param int $limit The maximum number of items to return. + * @return void + */ + public function set_item_limit(int $limit = 0) + { + $this->item_limit = $limit; + } + + /** + * Enable throwing exceptions + * + * @param bool $enable Should we throw exceptions, or use the old-style error property? + * @return void + */ + public function enable_exceptions(bool $enable = true) + { + $this->enable_exceptions = $enable; + } + + /** + * Initialize the feed object + * + * This is what makes everything happen. Period. This is where all of the + * configuration options get processed, feeds are fetched, cached, and + * parsed, and all of that other good stuff. + * + * @return bool True if successful, false otherwise + */ + public function init() + { + // Check absolute bare minimum requirements. + if (!extension_loaded('xml') || !extension_loaded('pcre')) { + $this->error = 'XML or PCRE extensions not loaded!'; + return false; + } + // Then check the xml extension is sane (i.e., libxml 2.7.x issue on PHP < 5.2.9 and libxml 2.7.0 to 2.7.2 on any version) if we don't have xmlreader. + elseif (!extension_loaded('xmlreader')) { + static $xml_is_sane = null; + if ($xml_is_sane === null) { + $parser_check = xml_parser_create(); + xml_parse_into_struct($parser_check, '<foo>&</foo>', $values); + if (\PHP_VERSION_ID < 80000) { + xml_parser_free($parser_check); + } + $xml_is_sane = isset($values[0]['value']); + } + if (!$xml_is_sane) { + return false; + } + } + + // The default sanitize class gets set in the constructor, check if it has + // changed. + if ($this->registry->get_class(Sanitize::class) !== Sanitize::class) { + $this->sanitize = $this->registry->create(Sanitize::class); + } + if (method_exists($this->sanitize, 'set_registry')) { + $this->sanitize->set_registry($this->registry); + } + + // Pass whatever was set with config options over to the sanitizer. + // Pass the classes in for legacy support; new classes should use the registry instead + $cache = $this->registry->get_class(Cache::class); + \assert($cache !== null, 'Cache must be defined'); + $this->sanitize->pass_cache_data( + $this->enable_cache, + $this->cache_location, + $this->cache_namefilter, + $cache, + $this->cache + ); + + $http_client = $this->get_http_client(); + + if ($http_client instanceof Psr18Client) { + $this->sanitize->set_http_client( + $http_client->getHttpClient(), + $http_client->getRequestFactory(), + $http_client->getUriFactory() + ); + } + + if (!empty($this->multifeed_url)) { + $i = 0; + $success = 0; + $this->multifeed_objects = []; + $this->error = []; + foreach ($this->multifeed_url as $url) { + $this->multifeed_objects[$i] = clone $this; + $this->multifeed_objects[$i]->set_feed_url($url); + $single_success = $this->multifeed_objects[$i]->init(); + $success |= $single_success; + if (!$single_success) { + $this->error[$i] = $this->multifeed_objects[$i]->error(); + } + $i++; + } + return (bool) $success; + } elseif ($this->feed_url === null && $this->raw_data === null) { + return false; + } + + $this->error = null; + $this->data = []; + $this->check_modified = false; + $this->multifeed_objects = []; + $cache = false; + + if ($this->feed_url !== null) { + $parsed_feed_url = $this->registry->call(Misc::class, 'parse_url', [$this->feed_url]); + + // Decide whether to enable caching + if ($this->enable_cache && $parsed_feed_url['scheme'] !== '') { + $cache = $this->get_cache($this->feed_url); + } + + // Fetch the data into $this->raw_data + if (($fetched = $this->fetch_data($cache)) === true) { + return true; + } elseif ($fetched === false) { + return false; + } + + [$headers, $sniffed] = $fetched; + } + + // Empty response check + if (empty($this->raw_data)) { + $this->error = "A feed could not be found at `$this->feed_url`. Empty body."; + $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]); + return false; + } + + // Set up array of possible encodings + $encodings = []; + + // First check to see if input has been overridden. + if ($this->input_encoding !== false) { + $encodings[] = strtoupper($this->input_encoding); + } + + $application_types = ['application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity']; + $text_types = ['text/xml', 'text/xml-external-parsed-entity']; + + // RFC 3023 (only applies to sniffed content) + if (isset($sniffed)) { + if (in_array($sniffed, $application_types) || substr($sniffed, 0, 12) === 'application/' && substr($sniffed, -4) === '+xml') { + if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) { + $encodings[] = strtoupper($charset[1]); + } + $encodings = array_merge($encodings, $this->registry->call(Misc::class, 'xml_encoding', [$this->raw_data, &$this->registry])); + $encodings[] = 'UTF-8'; + } elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml') { + if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) { + $encodings[] = strtoupper($charset[1]); + } + $encodings[] = 'US-ASCII'; + } + // Text MIME-type default + elseif (substr($sniffed, 0, 5) === 'text/') { + $encodings[] = 'UTF-8'; + } + } + + // Fallback to XML 1.0 Appendix F.1/UTF-8/ISO-8859-1 + $encodings = array_merge($encodings, $this->registry->call(Misc::class, 'xml_encoding', [$this->raw_data, &$this->registry])); + $encodings[] = 'UTF-8'; + $encodings[] = 'ISO-8859-1'; + + // There's no point in trying an encoding twice + $encodings = array_unique($encodings); + + // Loop through each possible encoding, till we return something, or run out of possibilities + foreach ($encodings as $encoding) { + // Change the encoding to UTF-8 (as we always use UTF-8 internally) + if ($utf8_data = $this->registry->call(Misc::class, 'change_encoding', [$this->raw_data, $encoding, 'UTF-8'])) { + // Create new parser + $parser = $this->registry->create(Parser::class); + + // If it's parsed fine + if ($parser->parse($utf8_data, 'UTF-8', $this->permanent_url ?? '')) { + $this->data = $parser->get_data(); + if (!($this->get_type() & ~self::TYPE_NONE)) { + $this->error = "A feed could not be found at `$this->feed_url`. This does not appear to be a valid RSS or Atom feed."; + $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]); + return false; + } + + if (isset($headers)) { + $this->data['headers'] = $headers; + } + $this->data['build'] = Misc::get_build(); + + // Cache the file if caching is enabled + $this->data['cache_expiration_time'] = $this->cache_duration + time(); + + if ($cache && !$cache->set_data($this->get_cache_filename($this->feed_url), $this->data, $this->cache_duration)) { + trigger_error("$this->cache_location is not writable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); + } + return true; + } + } + } + + if (isset($parser)) { + // We have an error, just set Misc::error to it and quit + $this->error = $this->feed_url; + $this->error .= sprintf(' is invalid XML, likely due to invalid characters. XML error: %s at line %d, column %d', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column()); + } else { + $this->error = 'The data could not be converted to UTF-8.'; + if (!extension_loaded('mbstring') && !extension_loaded('iconv') && !class_exists('\UConverter')) { + $this->error .= ' You MUST have either the iconv, mbstring or intl (PHP 5.5+) extension installed and enabled.'; + } else { + $missingExtensions = []; + if (!extension_loaded('iconv')) { + $missingExtensions[] = 'iconv'; + } + if (!extension_loaded('mbstring')) { + $missingExtensions[] = 'mbstring'; + } + if (!class_exists('\UConverter')) { + $missingExtensions[] = 'intl (PHP 5.5+)'; + } + $this->error .= ' Try installing/enabling the ' . implode(' or ', $missingExtensions) . ' extension.'; + } + } + + $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]); + + return false; + } + + /** + * Fetch the data + * + * If the data is already cached, attempt to fetch it from there instead + * + * @param Base|DataCache|false $cache Cache handler, or false to not load from the cache + * @return array{array<string, string>, string}|bool Returns true if the data was loaded from the cache, or an array of HTTP headers and sniffed type + */ + protected function fetch_data(&$cache) + { + if ($cache instanceof Base) { + // @trigger_error(sprintf('Providing $cache as "\SimplePie\Cache\Base" in %s() is deprecated since SimplePie 1.8.0, please provide "\SimplePie\Cache\DataCache" implementation instead.', __METHOD__), \E_USER_DEPRECATED); + $cache = new BaseDataCache($cache); + } + + // @phpstan-ignore-next-line Enforce PHPDoc type. + if ($cache !== false && !$cache instanceof DataCache) { + throw new InvalidArgumentException(sprintf( + '%s(): Argument #1 ($cache) must be of type %s|false', + __METHOD__, + DataCache::class + ), 1); + } + + $cacheKey = $this->get_cache_filename($this->feed_url); + + // If it's enabled, use the cache + if ($cache) { + // Load the Cache + $this->data = $cache->get_data($cacheKey, []); + + if (!empty($this->data)) { + // If the cache is for an outdated build of SimplePie + if (!isset($this->data['build']) || $this->data['build'] !== Misc::get_build()) { + $cache->delete_data($cacheKey); + $this->data = []; + } + // If we've hit a collision just rerun it with caching disabled + elseif (isset($this->data['url']) && $this->data['url'] !== $this->feed_url) { + $cache = false; + $this->data = []; + } + // If we've got a non feed_url stored (if the page isn't actually a feed, or is a redirect) use that URL. + elseif (isset($this->data['feed_url'])) { + // Do not need to do feed autodiscovery yet. + if ($this->data['feed_url'] !== $this->data['url']) { + $this->set_feed_url($this->data['feed_url']); + $this->data['url'] = $this->data['feed_url']; + + $cache->set_data($this->get_cache_filename($this->feed_url), $this->data, $this->autodiscovery_cache_duration); + + return $this->init(); + } + + $cache->delete_data($this->get_cache_filename($this->feed_url)); + $this->data = []; + } + // Check if the cache has been updated + elseif (!isset($this->data['cache_expiration_time']) || $this->data['cache_expiration_time'] < time()) { + // Want to know if we tried to send last-modified and/or etag headers + // when requesting this file. (Note that it's up to the file to + // support this, but we don't always send the headers either.) + $this->check_modified = true; + if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) { + $headers = [ + 'Accept' => SimplePie::DEFAULT_HTTP_ACCEPT_HEADER, + ]; + if (isset($this->data['headers']['last-modified'])) { + $headers['if-modified-since'] = $this->data['headers']['last-modified']; + } + if (isset($this->data['headers']['etag'])) { + $headers['if-none-match'] = $this->data['headers']['etag']; + } + + try { + $file = $this->get_http_client()->request(Client::METHOD_GET, $this->feed_url, $headers); + $this->status_code = $file->get_status_code(); + } catch (ClientException $th) { + $this->check_modified = false; + $this->status_code = 0; + + if ($this->force_cache_fallback) { + $this->data['cache_expiration_time'] = $this->cache_duration + time(); + $cache->set_data($cacheKey, $this->data, $this->cache_duration); + + return true; + } + + $failedFileReason = $th->getMessage(); + } + + if ($this->status_code === 304) { + // Set raw_data to false here too, to signify that the cache + // is still valid. + $this->raw_data = false; + $this->data['cache_expiration_time'] = $this->cache_duration + time(); + $cache->set_data($cacheKey, $this->data, $this->cache_duration); + + return true; + } + } + } + // If the cache is still valid, just return true + else { + $this->raw_data = false; + return true; + } + } + // If the cache is empty + else { + $this->data = []; + } + } + + // If we don't already have the file (it'll only exist if we've opened it to check if the cache has been modified), open it. + if (!isset($file)) { + if ($this->file instanceof File && $this->file->get_final_requested_uri() === $this->feed_url) { + $file = &$this->file; + } elseif (isset($failedFileReason)) { + // Do not try to fetch again if we already failed once. + // If the file connection had an error, set SimplePie::error to that and quit + $this->error = $failedFileReason; + + return !empty($this->data); + } else { + $headers = [ + 'Accept' => SimplePie::DEFAULT_HTTP_ACCEPT_HEADER, + ]; + try { + $file = $this->get_http_client()->request(Client::METHOD_GET, $this->feed_url, $headers); + } catch (ClientException $th) { + // If the file connection has an error, set SimplePie::error to that and quit + $this->error = $th->getMessage(); + + return !empty($this->data); + } + } + } + $this->status_code = $file->get_status_code(); + + // If the file connection has an error, set SimplePie::error to that and quit + if (!(!Misc::is_remote_uri($file->get_final_requested_uri()) || ($file->get_status_code() === 200 || $file->get_status_code() > 206 && $file->get_status_code() < 300))) { + $this->error = 'Retrieved unsupported status code "' . $this->status_code . '"'; + return !empty($this->data); + } + + if (!$this->force_feed) { + // Check if the supplied URL is a feed, if it isn't, look for it. + $locate = $this->registry->create(Locator::class, [ + (!$file instanceof File) ? File::fromResponse($file) : $file, + $this->timeout, + $this->useragent, + $this->max_checked_feeds, + $this->force_fsockopen, + $this->curl_options + ]); + + $http_client = $this->get_http_client(); + + if ($http_client instanceof Psr18Client) { + $locate->set_http_client( + $http_client->getHttpClient(), + $http_client->getRequestFactory(), + $http_client->getUriFactory() + ); + } + + if (!$locate->is_feed($file)) { + $copyStatusCode = $file->get_status_code(); + $copyContentType = $file->get_header_line('content-type'); + try { + $microformats = false; + if (class_exists('DOMXpath') && function_exists('Mf2\parse')) { + $doc = new \DOMDocument(); + @$doc->loadHTML($file->get_body_content()); + $xpath = new \DOMXpath($doc); + // Check for both h-feed and h-entry, as both a feed with no entries + // and a list of entries without an h-feed wrapper are both valid. + $query = '//*[contains(concat(" ", @class, " "), " h-feed ") or '. + 'contains(concat(" ", @class, " "), " h-entry ")]'; + + /** @var \DOMNodeList<\DOMElement> $result */ + $result = $xpath->query($query); + $microformats = $result->length !== 0; + } + // Now also do feed discovery, but if microformats were found don't + // overwrite the current value of file. + $discovered = $locate->find( + $this->autodiscovery, + $this->all_discovered_feeds + ); + if ($microformats) { + $hub = $locate->get_rel_link('hub'); + $self = $locate->get_rel_link('self'); + if ($hub || $self) { + $file = $this->store_links($file, $hub, $self); + } + // Push the current file onto all_discovered feeds so the user can + // be shown this as one of the options. + if ($this->all_discovered_feeds !== null) { + $this->all_discovered_feeds[] = $file; + } + } else { + if ($discovered) { + $file = $discovered; + } else { + // We need to unset this so that if SimplePie::set_file() has + // been called that object is untouched + unset($file); + $this->error = "A feed could not be found at `$this->feed_url`; the status code is `$copyStatusCode` and content-type is `$copyContentType`"; + $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]); + return false; + } + } + } catch (SimplePieException $e) { + // We need to unset this so that if SimplePie::set_file() has been called that object is untouched + unset($file); + // This is usually because DOMDocument doesn't exist + $this->error = $e->getMessage(); + $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, $e->getFile(), $e->getLine()]); + return false; + } + + if ($cache) { + $this->data = [ + 'url' => $this->feed_url, + 'feed_url' => $file->get_final_requested_uri(), + 'build' => Misc::get_build(), + 'cache_expiration_time' => $this->cache_duration + time(), + ]; + + if (!$cache->set_data($cacheKey, $this->data, $this->cache_duration)) { + trigger_error("$this->cache_location is not writable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); + } + } + } + $this->feed_url = $file->get_final_requested_uri(); + $locate = null; + } + + $this->raw_data = $file->get_body_content(); + $this->permanent_url = $file->get_permanent_uri(); + + $headers = []; + foreach ($file->get_headers() as $key => $values) { + $headers[$key] = implode(', ', $values); + } + + $sniffer = $this->registry->create(Sniffer::class, [&$file]); + $sniffed = $sniffer->get_type(); + + return [$headers, $sniffed]; + } + + /** + * Get the error message for the occurred error + * + * @return string|string[]|null Error message, or array of messages for multifeeds + */ + public function error() + { + return $this->error; + } + + /** + * Get the last HTTP status code + * + * @return int Status code + */ + public function status_code() + { + return $this->status_code; + } + + /** + * Get the raw XML + * + * This is the same as the old `$feed->enable_xml_dump(true)`, but returns + * the data instead of printing it. + * + * @return string|false Raw XML data, false if the cache is used + */ + public function get_raw_data() + { + return $this->raw_data; + } + + /** + * Get the character encoding used for output + * + * @since Preview Release + * @return string + */ + public function get_encoding() + { + return $this->sanitize->output_encoding; + } + + /** + * Send the content-type header with correct encoding + * + * This method ensures that the SimplePie-enabled page is being served with + * the correct {@link http://www.iana.org/assignments/media-types/ mime-type} + * and character encoding HTTP headers (character encoding determined by the + * {@see set_output_encoding} config option). + * + * This won't work properly if any content or whitespace has already been + * sent to the browser, because it relies on PHP's + * {@link http://php.net/header header()} function, and these are the + * circumstances under which the function works. + * + * Because it's setting these settings for the entire page (as is the nature + * of HTTP headers), this should only be used once per page (again, at the + * top). + * + * @param string $mime MIME type to serve the page as + * @return void + */ + public function handle_content_type(string $mime = 'text/html') + { + if (!headers_sent()) { + $header = "Content-type: $mime;"; + if ($this->get_encoding()) { + $header .= ' charset=' . $this->get_encoding(); + } else { + $header .= ' charset=UTF-8'; + } + header($header); + } + } + + /** + * Get the type of the feed + * + * This returns a self::TYPE_* constant, which can be tested against + * using {@link http://php.net/language.operators.bitwise bitwise operators} + * + * @since 0.8 (usage changed to using constants in 1.0) + * @see self::TYPE_NONE Unknown. + * @see self::TYPE_RSS_090 RSS 0.90. + * @see self::TYPE_RSS_091_NETSCAPE RSS 0.91 (Netscape). + * @see self::TYPE_RSS_091_USERLAND RSS 0.91 (Userland). + * @see self::TYPE_RSS_091 RSS 0.91. + * @see self::TYPE_RSS_092 RSS 0.92. + * @see self::TYPE_RSS_093 RSS 0.93. + * @see self::TYPE_RSS_094 RSS 0.94. + * @see self::TYPE_RSS_10 RSS 1.0. + * @see self::TYPE_RSS_20 RSS 2.0.x. + * @see self::TYPE_RSS_RDF RDF-based RSS. + * @see self::TYPE_RSS_SYNDICATION Non-RDF-based RSS (truly intended as syndication format). + * @see self::TYPE_RSS_ALL Any version of RSS. + * @see self::TYPE_ATOM_03 Atom 0.3. + * @see self::TYPE_ATOM_10 Atom 1.0. + * @see self::TYPE_ATOM_ALL Any version of Atom. + * @see self::TYPE_ALL Any known/supported feed type. + * @return int-mask-of<self::TYPE_*> constant + */ + public function get_type() + { + if (!isset($this->data['type'])) { + $this->data['type'] = self::TYPE_ALL; + if (isset($this->data['child'][self::NAMESPACE_ATOM_10]['feed'])) { + $this->data['type'] &= self::TYPE_ATOM_10; + } elseif (isset($this->data['child'][self::NAMESPACE_ATOM_03]['feed'])) { + $this->data['type'] &= self::TYPE_ATOM_03; + } elseif (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'])) { + if (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['channel']) + || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['image']) + || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['item']) + || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['textinput'])) { + $this->data['type'] &= self::TYPE_RSS_10; + } + if (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['channel']) + || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['image']) + || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['item']) + || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['textinput'])) { + $this->data['type'] &= self::TYPE_RSS_090; + } + } elseif (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'])) { + $this->data['type'] &= self::TYPE_RSS_ALL; + if (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) { + switch (trim($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) { + case '0.91': + $this->data['type'] &= self::TYPE_RSS_091; + if (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][self::NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) { + switch (trim($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][self::NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) { + case '0': + $this->data['type'] &= self::TYPE_RSS_091_NETSCAPE; + break; + + case '24': + $this->data['type'] &= self::TYPE_RSS_091_USERLAND; + break; + } + } + break; + + case '0.92': + $this->data['type'] &= self::TYPE_RSS_092; + break; + + case '0.93': + $this->data['type'] &= self::TYPE_RSS_093; + break; + + case '0.94': + $this->data['type'] &= self::TYPE_RSS_094; + break; + + case '2.0': + $this->data['type'] &= self::TYPE_RSS_20; + break; + } + } + } else { + $this->data['type'] = self::TYPE_NONE; + } + } + return $this->data['type']; + } + + /** + * Get the URL for the feed + * + * When the 'permanent' mode is enabled, returns the original feed URL, + * except in the case of an `HTTP 301 Moved Permanently` status response, + * in which case the location of the first redirection is returned. + * + * When the 'permanent' mode is disabled (default), + * may or may not be different from the URL passed to {@see set_feed_url()}, + * depending on whether auto-discovery was used, and whether there were + * any redirects along the way. + * + * @since Preview Release (previously called `get_feed_url()` since SimplePie 0.8.) + * @todo Support <itunes:new-feed-url> + * @todo Also, |atom:link|@rel=self + * @param bool $permanent Permanent mode to return only the original URL or the first redirection + * iff it is a 301 redirection + * @return string|null + */ + public function subscribe_url(bool $permanent = false) + { + if ($permanent) { + if ($this->permanent_url !== null) { + // sanitize encodes ampersands which are required when used in a url. + return str_replace( + '&', + '&', + $this->sanitize( + $this->permanent_url, + self::CONSTRUCT_IRI + ) + ); + } + } else { + if ($this->feed_url !== null) { + return str_replace( + '&', + '&', + $this->sanitize( + $this->feed_url, + self::CONSTRUCT_IRI + ) + ); + } + } + return null; + } + + /** + * Get data for an feed-level element + * + * This method allows you to get access to ANY element/attribute that is a + * sub-element of the opening feed tag. + * + * The return value is an indexed array of elements matching the given + * namespace and tag name. Each element has `attribs`, `data` and `child` + * subkeys. For `attribs` and `child`, these contain namespace subkeys. + * `attribs` then has one level of associative name => value data (where + * `value` is a string) after the namespace. `child` has tag-indexed keys + * after the namespace, each member of which is an indexed array matching + * this same format. + * + * For example: + * <pre> + * // This is probably a bad example because we already support + * // <media:content> natively, but it shows you how to parse through + * // the nodes. + * $group = $item->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'group'); + * $content = $group[0]['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content']; + * $file = $content[0]['attribs']['']['url']; + * echo $file; + * </pre> + * + * @since 1.0 + * @see http://simplepie.org/wiki/faq/supported_xml_namespaces + * @param string $namespace The URL of the XML namespace of the elements you're trying to access + * @param string $tag Tag name + * @return array<array<string, mixed>>|null + */ + public function get_feed_tags(string $namespace, string $tag) + { + $type = $this->get_type(); + if ($type & self::TYPE_ATOM_10) { + if (isset($this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag])) { + return $this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag]; + } + } + if ($type & self::TYPE_ATOM_03) { + if (isset($this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag])) { + return $this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag]; + } + } + if ($type & self::TYPE_RSS_RDF) { + if (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag])) { + return $this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag]; + } + } + if ($type & self::TYPE_RSS_SYNDICATION) { + if (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag])) { + return $this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag]; + } + } + return null; + } + + /** + * Get data for an channel-level element + * + * This method allows you to get access to ANY element/attribute in the + * channel/header section of the feed. + * + * See {@see SimplePie::get_feed_tags()} for a description of the return value + * + * @since 1.0 + * @see http://simplepie.org/wiki/faq/supported_xml_namespaces + * @param string $namespace The URL of the XML namespace of the elements you're trying to access + * @param string $tag Tag name + * @return array<array<string, mixed>>|null + */ + public function get_channel_tags(string $namespace, string $tag) + { + $type = $this->get_type(); + if ($type & self::TYPE_ATOM_ALL) { + if ($return = $this->get_feed_tags($namespace, $tag)) { + return $return; + } + } + if ($type & self::TYPE_RSS_10) { + if ($channel = $this->get_feed_tags(self::NAMESPACE_RSS_10, 'channel')) { + if (isset($channel[0]['child'][$namespace][$tag])) { + return $channel[0]['child'][$namespace][$tag]; + } + } + } + if ($type & self::TYPE_RSS_090) { + if ($channel = $this->get_feed_tags(self::NAMESPACE_RSS_090, 'channel')) { + if (isset($channel[0]['child'][$namespace][$tag])) { + return $channel[0]['child'][$namespace][$tag]; + } + } + } + if ($type & self::TYPE_RSS_SYNDICATION) { + if ($channel = $this->get_feed_tags(self::NAMESPACE_RSS_20, 'channel')) { + if (isset($channel[0]['child'][$namespace][$tag])) { + return $channel[0]['child'][$namespace][$tag]; + } + } + } + return null; + } + + /** + * Get data for an channel-level element + * + * This method allows you to get access to ANY element/attribute in the + * image/logo section of the feed. + * + * See {@see SimplePie::get_feed_tags()} for a description of the return value + * + * @since 1.0 + * @see http://simplepie.org/wiki/faq/supported_xml_namespaces + * @param string $namespace The URL of the XML namespace of the elements you're trying to access + * @param string $tag Tag name + * @return array<array<string, mixed>>|null + */ + public function get_image_tags(string $namespace, string $tag) + { + $type = $this->get_type(); + if ($type & self::TYPE_RSS_10) { + if ($image = $this->get_feed_tags(self::NAMESPACE_RSS_10, 'image')) { + if (isset($image[0]['child'][$namespace][$tag])) { + return $image[0]['child'][$namespace][$tag]; + } + } + } + if ($type & self::TYPE_RSS_090) { + if ($image = $this->get_feed_tags(self::NAMESPACE_RSS_090, 'image')) { + if (isset($image[0]['child'][$namespace][$tag])) { + return $image[0]['child'][$namespace][$tag]; + } + } + } + if ($type & self::TYPE_RSS_SYNDICATION) { + if ($image = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'image')) { + if (isset($image[0]['child'][$namespace][$tag])) { + return $image[0]['child'][$namespace][$tag]; + } + } + } + return null; + } + + /** + * Get the base URL value from the feed + * + * Uses `<xml:base>` if available, + * otherwise uses the first 'self' link or the first 'alternate' link of the feed, + * or failing that, the URL of the feed itself. + * + * @see get_link + * @see subscribe_url + * + * @param array<string, mixed> $element + * @return string + */ + public function get_base(array $element = []) + { + if (!empty($element['xml_base_explicit']) && isset($element['xml_base'])) { + return $element['xml_base']; + } + if (($link = $this->get_link(0, 'alternate')) !== null) { + return $link; + } + if (($link = $this->get_link(0, 'self')) !== null) { + return $link; + } + + return $this->subscribe_url() ?? ''; + } + + /** + * Sanitize feed data + * + * @access private + * @see Sanitize::sanitize() + * @param string $data Data to sanitize + * @param int-mask-of<SimplePie::CONSTRUCT_*> $type + * @param string $base Base URL to resolve URLs against + * @return string Sanitized data + */ + public function sanitize(string $data, int $type, string $base = '') + { + try { + // This really returns string|false but changing encoding is uncommon and we are going to deprecate it, so let’s just lie to PHPStan in the interest of cleaner annotations. + return $this->sanitize->sanitize($data, $type, $base); + } catch (SimplePieException $e) { + if (!$this->enable_exceptions) { + $this->error = $e->getMessage(); + $this->registry->call(Misc::class, 'error', [$this->error, E_USER_WARNING, $e->getFile(), $e->getLine()]); + return ''; + } + + throw $e; + } + } + + /** + * Get the title of the feed + * + * Uses `<atom:title>`, `<title>` or `<dc:title>` + * + * @since 1.0 (previously called `get_feed_title` since 0.8) + * @return string|null + */ + public function get_title() + { + if ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'title')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'title')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_10, 'title')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_090, 'title')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'title')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'title')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'title')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } + + return null; + } + + /** + * Get a category for the feed + * + * @since Unknown + * @param int $key The category that you want to return. Remember that arrays begin with 0, not 1 + * @return Category|null + */ + public function get_category(int $key = 0) + { + $categories = $this->get_categories(); + if (isset($categories[$key])) { + return $categories[$key]; + } + + return null; + } + + /** + * Get all categories for the feed + * + * Uses `<atom:category>`, `<category>` or `<dc:subject>` + * + * @since Unknown + * @return array<Category>|null List of {@see Category} objects + */ + public function get_categories() + { + $categories = []; + + foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'category') as $category) { + $term = null; + $scheme = null; + $label = null; + if (isset($category['attribs']['']['term'])) { + $term = $this->sanitize($category['attribs']['']['term'], self::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) { + $scheme = $this->sanitize($category['attribs']['']['scheme'], self::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['label'])) { + $label = $this->sanitize($category['attribs']['']['label'], self::CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]); + } + foreach ((array) $this->get_channel_tags(self::NAMESPACE_RSS_20, 'category') as $category) { + // This is really the label, but keep this as the term also for BC. + // Label will also work on retrieving because that falls back to term. + $term = $this->sanitize($category['data'], self::CONSTRUCT_TEXT); + if (isset($category['attribs']['']['domain'])) { + $scheme = $this->sanitize($category['attribs']['']['domain'], self::CONSTRUCT_TEXT); + } else { + $scheme = null; + } + $categories[] = $this->registry->create(Category::class, [$term, $scheme, null]); + } + foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_11, 'subject') as $category) { + $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], self::CONSTRUCT_TEXT), null, null]); + } + foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_10, 'subject') as $category) { + $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], self::CONSTRUCT_TEXT), null, null]); + } + + if (!empty($categories)) { + return array_unique($categories); + } + + return null; + } + + /** + * Get an author for the feed + * + * @since 1.1 + * @param int $key The author that you want to return. Remember that arrays begin with 0, not 1 + * @return Author|null + */ + public function get_author(int $key = 0) + { + $authors = $this->get_authors(); + if (isset($authors[$key])) { + return $authors[$key]; + } + + return null; + } + + /** + * Get all authors for the feed + * + * Uses `<atom:author>`, `<author>`, `<dc:creator>` or `<itunes:author>` + * + * @since 1.1 + * @return array<Author>|null List of {@see Author} objects + */ + public function get_authors() + { + $authors = []; + foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'author') as $author) { + $name = null; + $uri = null; + $email = null; + if (isset($author['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'])) { + $name = $this->sanitize($author['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'], self::CONSTRUCT_TEXT); + } + if (isset($author['child'][self::NAMESPACE_ATOM_10]['uri'][0]['data'])) { + $uri = $author['child'][self::NAMESPACE_ATOM_10]['uri'][0]; + $uri = $this->sanitize($uri['data'], self::CONSTRUCT_IRI, $this->get_base($uri)); + } + if (isset($author['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'])) { + $email = $this->sanitize($author['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'], self::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) { + $authors[] = $this->registry->create(Author::class, [$name, $uri, $email]); + } + } + if ($author = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'author')) { + $name = null; + $url = null; + $email = null; + if (isset($author[0]['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'])) { + $name = $this->sanitize($author[0]['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'], self::CONSTRUCT_TEXT); + } + if (isset($author[0]['child'][self::NAMESPACE_ATOM_03]['url'][0]['data'])) { + $url = $author[0]['child'][self::NAMESPACE_ATOM_03]['url'][0]; + $url = $this->sanitize($url['data'], self::CONSTRUCT_IRI, $this->get_base($url)); + } + if (isset($author[0]['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'])) { + $email = $this->sanitize($author[0]['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'], self::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) { + $authors[] = $this->registry->create(Author::class, [$name, $url, $email]); + } + } + foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_11, 'creator') as $author) { + $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], self::CONSTRUCT_TEXT), null, null]); + } + foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_10, 'creator') as $author) { + $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], self::CONSTRUCT_TEXT), null, null]); + } + foreach ((array) $this->get_channel_tags(self::NAMESPACE_ITUNES, 'author') as $author) { + $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], self::CONSTRUCT_TEXT), null, null]); + } + + if (!empty($authors)) { + return array_unique($authors); + } + + return null; + } + + /** + * Get a contributor for the feed + * + * @since 1.1 + * @param int $key The contrbutor that you want to return. Remember that arrays begin with 0, not 1 + * @return Author|null + */ + public function get_contributor(int $key = 0) + { + $contributors = $this->get_contributors(); + if (isset($contributors[$key])) { + return $contributors[$key]; + } + + return null; + } + + /** + * Get all contributors for the feed + * + * Uses `<atom:contributor>` + * + * @since 1.1 + * @return array<Author>|null List of {@see Author} objects + */ + public function get_contributors() + { + $contributors = []; + foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'contributor') as $contributor) { + $name = null; + $uri = null; + $email = null; + if (isset($contributor['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'])) { + $name = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'], self::CONSTRUCT_TEXT); + } + if (isset($contributor['child'][self::NAMESPACE_ATOM_10]['uri'][0]['data'])) { + $uri = $contributor['child'][self::NAMESPACE_ATOM_10]['uri'][0]; + $uri = $this->sanitize($uri['data'], self::CONSTRUCT_IRI, $this->get_base($uri)); + } + if (isset($contributor['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'])) { + $email = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'], self::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) { + $contributors[] = $this->registry->create(Author::class, [$name, $uri, $email]); + } + } + foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'contributor') as $contributor) { + $name = null; + $url = null; + $email = null; + if (isset($contributor['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'])) { + $name = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'], self::CONSTRUCT_TEXT); + } + if (isset($contributor['child'][self::NAMESPACE_ATOM_03]['url'][0]['data'])) { + $url = $contributor['child'][self::NAMESPACE_ATOM_03]['url'][0]; + $url = $this->sanitize($url['data'], self::CONSTRUCT_IRI, $this->get_base($url)); + } + if (isset($contributor['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'])) { + $email = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'], self::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) { + $contributors[] = $this->registry->create(Author::class, [$name, $url, $email]); + } + } + + if (!empty($contributors)) { + return array_unique($contributors); + } + + return null; + } + + /** + * Get a single link for the feed + * + * @since 1.0 (previously called `get_feed_link` since Preview Release, `get_feed_permalink()` since 0.8) + * @param int $key The link that you want to return. Remember that arrays begin with 0, not 1 + * @param string $rel The relationship of the link to return + * @return string|null Link URL + */ + public function get_link(int $key = 0, string $rel = 'alternate') + { + $links = $this->get_links($rel); + if (isset($links[$key])) { + return $links[$key]; + } + + return null; + } + + /** + * Get the permalink for the item + * + * Returns the first link available with a relationship of "alternate". + * Identical to {@see get_link()} with key 0 + * + * @see get_link + * @since 1.0 (previously called `get_feed_link` since Preview Release, `get_feed_permalink()` since 0.8) + * @internal Added for parity between the parent-level and the item/entry-level. + * @return string|null Link URL + */ + public function get_permalink() + { + return $this->get_link(0); + } + + /** + * Get all links for the feed + * + * Uses `<atom:link>` or `<link>` + * + * @since Beta 2 + * @param string $rel The relationship of links to return + * @return array<string>|null Links found for the feed (strings) + */ + public function get_links(string $rel = 'alternate') + { + if (!isset($this->data['links'])) { + $this->data['links'] = []; + if ($links = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'link')) { + foreach ($links as $link) { + if (isset($link['attribs']['']['href'])) { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], self::CONSTRUCT_IRI, $this->get_base($link)); + } + } + } + if ($links = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'link')) { + foreach ($links as $link) { + if (isset($link['attribs']['']['href'])) { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], self::CONSTRUCT_IRI, $this->get_base($link)); + } + } + } + if ($links = $this->get_channel_tags(self::NAMESPACE_RSS_10, 'link')) { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], self::CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_channel_tags(self::NAMESPACE_RSS_090, 'link')) { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], self::CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'link')) { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], self::CONSTRUCT_IRI, $this->get_base($links[0])); + } + + $keys = array_keys($this->data['links']); + foreach ($keys as $key) { + if ($this->registry->call(Misc::class, 'is_isegment_nz_nc', [$key])) { + if (isset($this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key])) { + $this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key]); + $this->data['links'][$key] = &$this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key]; + } else { + $this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key] = &$this->data['links'][$key]; + } + } elseif (substr($key, 0, 41) === self::IANA_LINK_RELATIONS_REGISTRY) { + $this->data['links'][substr($key, 41)] = &$this->data['links'][$key]; + } + $this->data['links'][$key] = array_unique($this->data['links'][$key]); + } + } + + if (isset($this->data['headers']['link'])) { + $link_headers = $this->data['headers']['link']; + if (is_array($link_headers)) { + $link_headers = implode(',', $link_headers); + } + // https://datatracker.ietf.org/doc/html/rfc8288 + if (is_string($link_headers) && + preg_match_all('/<(?P<uri>[^>]+)>\s*;\s*rel\s*=\s*(?P<quote>"?)' . preg_quote($rel) . '(?P=quote)\s*(?=,|$)/i', $link_headers, $matches)) { + return $matches['uri']; + } + } + + if (isset($this->data['links'][$rel])) { + return $this->data['links'][$rel]; + } + + return null; + } + + /** + * @return ?array<Response> + */ + public function get_all_discovered_feeds() + { + return $this->all_discovered_feeds; + } + + /** + * Get the content for the item + * + * Uses `<atom:subtitle>`, `<atom:tagline>`, `<description>`, + * `<dc:description>`, `<itunes:summary>` or `<itunes:subtitle>` + * + * @since 1.0 (previously called `get_feed_description()` since 0.8) + * @return string|null + */ + public function get_description() + { + if ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'subtitle')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'tagline')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_10, 'description')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_090, 'description')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'description')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'description')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'description')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ITUNES, 'summary')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ITUNES, 'subtitle')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_HTML, $this->get_base($return[0])); + } + + return null; + } + + /** + * Get the copyright info for the feed + * + * Uses `<atom:rights>`, `<atom:copyright>` or `<dc:rights>` + * + * @since 1.0 (previously called `get_feed_copyright()` since 0.8) + * @return string|null + */ + public function get_copyright() + { + if ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'rights')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'copyright')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'copyright')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'rights')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'rights')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } + + return null; + } + + /** + * Get the language for the feed + * + * Uses `<language>`, `<dc:language>`, or @xml_lang + * + * @since 1.0 (previously called `get_feed_language()` since 0.8) + * @return string|null + */ + public function get_language() + { + if ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'language')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'language')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'language')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif (isset($this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['xml_lang'])) { + return $this->sanitize($this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['xml_lang'], self::CONSTRUCT_TEXT); + } elseif (isset($this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['xml_lang'])) { + return $this->sanitize($this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['xml_lang'], self::CONSTRUCT_TEXT); + } elseif (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['xml_lang'])) { + return $this->sanitize($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['xml_lang'], self::CONSTRUCT_TEXT); + } elseif (isset($this->data['headers']['content-language'])) { + return $this->sanitize($this->data['headers']['content-language'], self::CONSTRUCT_TEXT); + } + + return null; + } + + /** + * Get the latitude coordinates for the item + * + * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications + * + * Uses `<geo:lat>` or `<georss:point>` + * + * @since 1.0 + * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo + * @link http://www.georss.org/ GeoRSS + * @return float|null + */ + public function get_latitude() + { + if ($return = $this->get_channel_tags(self::NAMESPACE_W3C_BASIC_GEO, 'lat')) { + return (float) $return[0]['data']; + } elseif (($return = $this->get_channel_tags(self::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { + return (float) $match[1]; + } + + return null; + } + + /** + * Get the longitude coordinates for the feed + * + * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications + * + * Uses `<geo:long>`, `<geo:lon>` or `<georss:point>` + * + * @since 1.0 + * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo + * @link http://www.georss.org/ GeoRSS + * @return float|null + */ + public function get_longitude() + { + if ($return = $this->get_channel_tags(self::NAMESPACE_W3C_BASIC_GEO, 'long')) { + return (float) $return[0]['data']; + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_W3C_BASIC_GEO, 'lon')) { + return (float) $return[0]['data']; + } elseif (($return = $this->get_channel_tags(self::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { + return (float) $match[2]; + } + + return null; + } + + /** + * Get the feed logo's title + * + * RSS 0.9.0, 1.0 and 2.0 feeds are allowed to have a "feed logo" title. + * + * Uses `<image><title>` or `<image><dc:title>` + * + * @return string|null + */ + public function get_image_title() + { + if ($return = $this->get_image_tags(self::NAMESPACE_RSS_10, 'title')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_090, 'title')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'title')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_image_tags(self::NAMESPACE_DC_11, 'title')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } elseif ($return = $this->get_image_tags(self::NAMESPACE_DC_10, 'title')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); + } + + return null; + } + + /** + * Get the feed logo's URL + * + * RSS 0.9.0, 2.0, Atom 1.0, and feeds with iTunes RSS tags are allowed to + * have a "feed logo" URL. This points directly to the image itself. + * + * Uses `<itunes:image>`, `<atom:logo>`, `<atom:icon>`, + * `<image><title>` or `<image><dc:title>` + * + * @return string|null + */ + public function get_image_url() + { + if ($return = $this->get_channel_tags(self::NAMESPACE_ITUNES, 'image')) { + return $this->sanitize($return[0]['attribs']['']['href'], self::CONSTRUCT_IRI); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'logo')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); + } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'icon')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); + } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_10, 'url')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); + } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_090, 'url')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); + } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'url')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); + } + + return null; + } + + /** + * Get the feed logo's link + * + * RSS 0.9.0, 1.0 and 2.0 feeds are allowed to have a "feed logo" link. This + * points to a human-readable page that the image should link to. + * + * Uses `<itunes:image>`, `<atom:logo>`, `<atom:icon>`, + * `<image><title>` or `<image><dc:title>` + * + * @return string|null + */ + public function get_image_link() + { + if ($return = $this->get_image_tags(self::NAMESPACE_RSS_10, 'link')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); + } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_090, 'link')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); + } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'link')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); + } + + return null; + } + + /** + * Get the feed logo's link + * + * RSS 2.0 feeds are allowed to have a "feed logo" width. + * + * Uses `<image><width>` or defaults to 88 if no width is specified and + * the feed is an RSS 2.0 feed. + * + * @return int|null + */ + public function get_image_width() + { + if ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'width')) { + return intval($return[0]['data']); + } elseif ($this->get_type() & self::TYPE_RSS_SYNDICATION && $this->get_image_tags(self::NAMESPACE_RSS_20, 'url')) { + return 88; + } + + return null; + } + + /** + * Get the feed logo's height + * + * RSS 2.0 feeds are allowed to have a "feed logo" height. + * + * Uses `<image><height>` or defaults to 31 if no height is specified and + * the feed is an RSS 2.0 feed. + * + * @return int|null + */ + public function get_image_height() + { + if ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'height')) { + return intval($return[0]['data']); + } elseif ($this->get_type() & self::TYPE_RSS_SYNDICATION && $this->get_image_tags(self::NAMESPACE_RSS_20, 'url')) { + return 31; + } + + return null; + } + + /** + * Get the number of items in the feed + * + * This is well-suited for {@link http://php.net/for for()} loops with + * {@see get_item()} + * + * @param int $max Maximum value to return. 0 for no limit + * @return int Number of items in the feed + */ + public function get_item_quantity(int $max = 0) + { + $qty = count($this->get_items()); + if ($max === 0) { + return $qty; + } + + return min($qty, $max); + } + + /** + * Get a single item from the feed + * + * This is better suited for {@link http://php.net/for for()} loops, whereas + * {@see get_items()} is better suited for + * {@link http://php.net/foreach foreach()} loops. + * + * @see get_item_quantity() + * @since Beta 2 + * @param int $key The item that you want to return. Remember that arrays begin with 0, not 1 + * @return Item|null + */ + public function get_item(int $key = 0) + { + $items = $this->get_items(); + if (isset($items[$key])) { + return $items[$key]; + } + + return null; + } + + /** + * Get all items from the feed + * + * This is better suited for {@link http://php.net/for for()} loops, whereas + * {@see get_items()} is better suited for + * {@link http://php.net/foreach foreach()} loops. + * + * @see get_item_quantity + * @since Beta 2 + * @param int $start Index to start at + * @param int $end Number of items to return. 0 for all items after `$start` + * @return Item[] List of {@see Item} objects + */ + public function get_items(int $start = 0, int $end = 0) + { + if (!isset($this->data['items'])) { + if (!empty($this->multifeed_objects)) { + $this->data['items'] = SimplePie::merge_items($this->multifeed_objects, $start, $end, $this->item_limit); + if (empty($this->data['items'])) { + return []; + } + return $this->data['items']; + } + $this->data['items'] = []; + if ($items = $this->get_feed_tags(self::NAMESPACE_ATOM_10, 'entry')) { + $keys = array_keys($items); + foreach ($keys as $key) { + $this->data['items'][] = $this->make_item($items[$key]); + } + } + if ($items = $this->get_feed_tags(self::NAMESPACE_ATOM_03, 'entry')) { + $keys = array_keys($items); + foreach ($keys as $key) { + $this->data['items'][] = $this->make_item($items[$key]); + } + } + if ($items = $this->get_feed_tags(self::NAMESPACE_RSS_10, 'item')) { + $keys = array_keys($items); + foreach ($keys as $key) { + $this->data['items'][] = $this->make_item($items[$key]); + } + } + if ($items = $this->get_feed_tags(self::NAMESPACE_RSS_090, 'item')) { + $keys = array_keys($items); + foreach ($keys as $key) { + $this->data['items'][] = $this->make_item($items[$key]); + } + } + if ($items = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'item')) { + $keys = array_keys($items); + foreach ($keys as $key) { + $this->data['items'][] = $this->make_item($items[$key]); + } + } + } + + if (empty($this->data['items'])) { + return []; + } + + if ($this->order_by_date) { + if (!isset($this->data['ordered_items'])) { + $this->data['ordered_items'] = $this->data['items']; + usort($this->data['ordered_items'], [get_class($this), 'sort_items']); + } + $items = $this->data['ordered_items']; + } else { + $items = $this->data['items']; + } + // Slice the data as desired + if ($end === 0) { + return array_slice($items, $start); + } + + return array_slice($items, $start, $end); + } + + /** + * Set the favicon handler + * + * @deprecated Use your own favicon handling instead + * @param string|false $page + * @return bool + */ + public function set_favicon_handler($page = false, string $qs = 'i') + { + trigger_error('Favicon handling has been removed since SimplePie 1.3, please use your own handling', \E_USER_DEPRECATED); + return false; + } + + /** + * Get the favicon for the current feed + * + * @deprecated Use your own favicon handling instead + * @return string|bool + */ + public function get_favicon() + { + trigger_error('Favicon handling has been removed since SimplePie 1.3, please use your own handling', \E_USER_DEPRECATED); + + if (($url = $this->get_link()) !== null) { + return 'https://www.google.com/s2/favicons?domain=' . urlencode($url); + } + + return false; + } + + /** + * Magic method handler + * + * @param string $method Method name + * @param array<mixed> $args Arguments to the method + * @return mixed + */ + public function __call(string $method, array $args) + { + if (strpos($method, 'subscribe_') === 0) { + trigger_error('subscribe_*() has been deprecated since SimplePie 1.3, implement the callback yourself', \E_USER_DEPRECATED); + return ''; + } + if ($method === 'enable_xml_dump') { + trigger_error('enable_xml_dump() has been deprecated since SimplePie 1.3, use get_raw_data() instead', \E_USER_DEPRECATED); + return false; + } + + $class = get_class($this); + $trace = debug_backtrace(); + $file = $trace[0]['file'] ?? ''; + $line = $trace[0]['line'] ?? ''; + throw new SimplePieException("Call to undefined method $class::$method() in $file on line $line"); + } + + /** + * Item factory + * + * @param array<string, mixed> $data + */ + private function make_item(array $data): Item + { + $item = $this->registry->create(Item::class, [$this, $data]); + $item->set_sanitize($this->sanitize); + + return $item; + } + + /** + * Sorting callback for items + * + * @access private + * @param Item $a + * @param Item $b + * @return -1|0|1 + */ + public static function sort_items(Item $a, Item $b) + { + $a_date = $a->get_date('U'); + $b_date = $b->get_date('U'); + if ($a_date && $b_date) { + return $a_date > $b_date ? -1 : 1; + } + // Sort items without dates to the top. + if ($a_date) { + return 1; + } + if ($b_date) { + return -1; + } + return 0; + } + + /** + * Merge items from several feeds into one + * + * If you're merging multiple feeds together, they need to all have dates + * for the items or else SimplePie will refuse to sort them. + * + * @link http://simplepie.org/wiki/tutorial/sort_multiple_feeds_by_time_and_date#if_feeds_require_separate_per-feed_settings + * @param array<SimplePie> $urls List of SimplePie feed objects to merge + * @param int $start Starting item + * @param int $end Number of items to return + * @param int $limit Maximum number of items per feed + * @return array<Item> + */ + public static function merge_items(array $urls, int $start = 0, int $end = 0, int $limit = 0) + { + if (count($urls) > 0) { + $items = []; + foreach ($urls as $arg) { + if ($arg instanceof SimplePie) { + $items = array_merge($items, $arg->get_items(0, $limit)); + + // @phpstan-ignore-next-line Enforce PHPDoc type. + } else { + trigger_error('Arguments must be SimplePie objects', E_USER_WARNING); + } + } + + usort($items, [get_class($urls[0]), 'sort_items']); + + if ($end === 0) { + return array_slice($items, $start); + } + + return array_slice($items, $start, $end); + } + + trigger_error('Cannot merge zero SimplePie objects', E_USER_WARNING); + return []; + } + + /** + * Store PubSubHubbub links as headers + * + * There is no way to find PuSH links in the body of a microformats feed, + * so they are added to the headers when found, to be used later by get_links. + */ + private function store_links(Response $file, ?string $hub, ?string $self): Response + { + $linkHeaderLine = $file->get_header_line('link'); + $linkHeader = $file->get_header('link'); + + if ($hub && !preg_match('/rel=hub/', $linkHeaderLine)) { + $linkHeader[] = '<'.$hub.'>; rel=hub'; + } + + if ($self && !preg_match('/rel=self/', $linkHeaderLine)) { + $linkHeader[] = '<'.$self.'>; rel=self'; + } + + if (count($linkHeader) > 0) { + $file = $file->with_header('link', $linkHeader); + } + + return $file; + } + + /** + * Get a DataCache + * + * @param string $feed_url Only needed for BC, can be removed in SimplePie 2.0.0 + * + * @return DataCache + */ + private function get_cache(string $feed_url = ''): DataCache + { + if ($this->cache === null) { + // @trigger_error(sprintf('Not providing as PSR-16 cache implementation is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache()".'), \E_USER_DEPRECATED); + $cache = $this->registry->call(Cache::class, 'get_handler', [ + $this->cache_location, + $this->get_cache_filename($feed_url), + Base::TYPE_FEED + ]); + + return new BaseDataCache($cache); + } + + return $this->cache; + } + + /** + * Get a HTTP client + */ + private function get_http_client(): Client + { + if ($this->http_client === null) { + $this->http_client = new FileClient( + $this->get_registry(), + [ + 'timeout' => $this->timeout, + 'redirects' => 5, + 'useragent' => $this->useragent, + 'force_fsockopen' => $this->force_fsockopen, + 'curl_options' => $this->curl_options, + ] + ); + $this->http_client_injected = true; + } + + return $this->http_client; + } +} + +class_alias('SimplePie\SimplePie', 'SimplePie'); + +} + +namespace SimplePie { + +/** + * Manages all author-related data + * + * Used by {@see Item::get_author()} and {@see SimplePie::get_authors()} + * + * This class can be overloaded with {@see SimplePie::set_author_class()} + */ +class Author +{ + /** + * Author's name + * + * @var ?string + * @see get_name() + */ + public $name; + + /** + * Author's link + * + * @var ?string + * @see get_link() + */ + public $link; + + /** + * Author's email address + * + * @var ?string + * @see get_email() + */ + public $email; + + /** + * Constructor, used to input the data + */ + public function __construct( + ?string $name = null, + ?string $link = null, + ?string $email = null + ) { + $this->name = $name; + $this->link = $link; + $this->email = $email; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Author's name + * + * @return string|null + */ + public function get_name() + { + if ($this->name !== null) { + return $this->name; + } + + return null; + } + + /** + * Author's link + * + * @return string|null + */ + public function get_link() + { + if ($this->link !== null) { + return $this->link; + } + + return null; + } + + /** + * Author's email address + * + * @return string|null + */ + public function get_email() + { + if ($this->email !== null) { + return $this->email; + } + + return null; + } +} + +class_alias('SimplePie\Author', 'SimplePie_Author'); + +} + +namespace SimplePie { + +use SimplePie\Cache\Base; + +/** + * Used to create cache objects + * + * This class can be overloaded with {@see SimplePie::set_cache_class()}, + * although the preferred way is to create your own handler + * via {@see register()} + * + * @deprecated since SimplePie 1.8.0, use "SimplePie\SimplePie::set_cache()" instead + */ +class Cache +{ + /** + * Cache handler classes + * + * These receive 3 parameters to their constructor, as documented in + * {@see register()} + * @var array<string, class-string<Base>> + */ + protected static $handlers = [ + 'mysql' => Cache\MySQL::class, + 'memcache' => Cache\Memcache::class, + 'memcached' => Cache\Memcached::class, + 'redis' => Cache\Redis::class, + ]; + + /** + * Don't call the constructor. Please. + */ + private function __construct() + { + } + + /** + * Create a new SimplePie\Cache object + * + * @param string $location URL location (scheme is used to determine handler) + * @param string $filename Unique identifier for cache object + * @param Base::TYPE_FEED|Base::TYPE_IMAGE $extension 'spi' or 'spc' + * @return Base Type of object depends on scheme of `$location` + */ + public static function get_handler(string $location, string $filename, $extension) + { + $type = explode(':', $location, 2); + $type = $type[0]; + if (!empty(self::$handlers[$type])) { + $class = self::$handlers[$type]; + return new $class($location, $filename, $extension); + } + + return new \SimplePie\Cache\File($location, $filename, $extension); + } + + /** + * Create a new SimplePie\Cache object + * + * @deprecated since SimplePie 1.3.1, use {@see get_handler()} instead + * @param string $location + * @param string $filename + * @param Base::TYPE_FEED|Base::TYPE_IMAGE $extension + * @return Base + */ + public function create(string $location, string $filename, $extension) + { + trigger_error('Cache::create() has been replaced with Cache::get_handler() since SimplePie 1.3.1, use the registry system instead.', \E_USER_DEPRECATED); + + return self::get_handler($location, $filename, $extension); + } + + /** + * Register a handler + * + * @param string $type DSN type to register for + * @param class-string<Base> $class Name of handler class. Must implement Base + * @return void + */ + public static function register(string $type, $class) + { + self::$handlers[$type] = $class; + } + + /** + * Parse a URL into an array + * + * @param string $url + * @return array<string, mixed> + */ + public static function parse_URL(string $url) + { + $parsedUrl = parse_url($url); + + if ($parsedUrl === false) { + return []; + } + + $params = array_merge($parsedUrl, ['extras' => []]); + if (isset($params['query'])) { + parse_str($params['query'], $params['extras']); + } + return $params; + } +} + +class_alias('SimplePie\Cache', 'SimplePie_Cache'); + +} + +namespace SimplePie\Cache { + +/** + * Base for cache objects + * + * Classes to be used with {@see \SimplePie\Cache::register()} are expected + * to implement this interface. + * + * @deprecated since SimplePie 1.8.0, use "Psr\SimpleCache\CacheInterface" instead + */ +interface Base +{ + /** + * Feed cache type + * + * @var string + */ + public const TYPE_FEED = 'spc'; + + /** + * Image cache type + * + * @var string + */ + public const TYPE_IMAGE = 'spi'; + + /** + * Create a new cache object + * + * @param string $location Location string (from SimplePie::$cache_location) + * @param string $name Unique ID for the cache + * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data + */ + public function __construct(string $location, string $name, $type); + + /** + * Save data to the cache + * + * @param array<mixed>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property + * @return bool Successfulness + */ + public function save($data); + + /** + * Retrieve the data saved to the cache + * + * @return array<mixed> Data for SimplePie::$data + */ + public function load(); + + /** + * Retrieve the last modified time for the cache + * + * @return int Timestamp + */ + public function mtime(); + + /** + * Set the last modified time to the current time + * + * @return bool Success status + */ + public function touch(); + + /** + * Remove the cache + * + * @return bool Success status + */ + public function unlink(); +} + +class_alias('SimplePie\Cache\Base', 'SimplePie_Cache_Base'); + +} + +namespace SimplePie\Cache { + +use InvalidArgumentException; + +/** + * Adapter for deprecated \SimplePie\Cache\Base implementations + * + * @internal + */ +final class BaseDataCache implements DataCache +{ + /** + * @var Base + */ + private $cache; + + public function __construct(Base $cache) + { + $this->cache = $cache; + } + + /** + * Fetches a value from the cache. + * + * Equivalent to \Psr\SimpleCache\CacheInterface::get() + * <code> + * public function get(string $key, mixed $default = null): mixed; + * </code> + * + * @param string $key The unique key of this item in the cache. + * @param mixed $default Default value to return if the key does not exist. + * + * @return array|mixed The value of the item from the cache, or $default in case of cache miss. + * + * @throws InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function get_data(string $key, $default = null) + { + $data = $this->cache->load(); + + if (!is_array($data)) { + return $default; + } + + // ignore data if internal cache expiration time is not set + if (!array_key_exists('__cache_expiration_time', $data)) { + return $default; + } + + // ignore data if internal cache expiration time is expired + if ($data['__cache_expiration_time'] < time()) { + return $default; + } + + // remove internal cache expiration time + unset($data['__cache_expiration_time']); + + return $data; + } + + /** + * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. + * + * Equivalent to \Psr\SimpleCache\CacheInterface::set() + * <code> + * public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool; + * </code> + * + * @param string $key The key of the item to store. + * @param array<mixed> $value The value of the item to store, must be serializable. + * @param null|int $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function set_data(string $key, array $value, ?int $ttl = null): bool + { + if ($ttl === null) { + $ttl = 3600; + } + + // place internal cache expiration time + $value['__cache_expiration_time'] = time() + $ttl; + + return $this->cache->save($value); + } + + /** + * Delete an item from the cache by its unique key. + * + * Equivalent to \Psr\SimpleCache\CacheInterface::delete() + * <code> + * public function delete(string $key): bool; + * </code> + * + * @param string $key The unique cache key of the item to delete. + * + * @return bool True if the item was successfully removed. False if there was an error. + * + * @throws InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function delete_data(string $key): bool + { + return $this->cache->unlink(); + } +} + +} + +namespace SimplePie\Cache { + +/** + * Creating a cache filename with callables + */ +final class CallableNameFilter implements NameFilter +{ + /** + * @var callable(string): string + */ + private $callable; + + /** + * @param callable(string): string $callable + */ + public function __construct(callable $callable) + { + $this->callable = $callable; + } + + /** + * Method to create cache filename with. + * + * The returning name MUST follow the rules for keys in PSR-16. + * + * @link https://www.php-fig.org/psr/psr-16/ + * + * The returning name MUST be a string of at least one character + * that uniquely identifies a cached item, MUST only contain the + * characters A-Z, a-z, 0-9, _, and . in any order in UTF-8 encoding + * and MUST not longer then 64 characters. The following characters + * are reserved for future extensions and MUST NOT be used: {}()/\@: + * + * A provided implementing library MAY support additional characters + * and encodings or longer lengths, but MUST support at least that + * minimum. + * + * @param string $name The name for the cache will be most likely an url with query string + * + * @return string the new cache name + */ + public function filter(string $name): string + { + return call_user_func($this->callable, $name); + } +} + +} + +namespace SimplePie\Cache { + +use SimplePie\Item; + +/** + * Base class for database-based caches + * + * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead + */ +abstract class DB implements Base +{ + /** + * Helper for database conversion + * + * Converts a given {@see SimplePie} object into data to be stored + * + * @param \SimplePie\SimplePie $data + * @return array{string, array<string, Item>} First item is the serialized data for storage, second item is the unique ID for this item + */ + protected static function prepare_simplepie_object_for_cache(\SimplePie\SimplePie $data) + { + $items = $data->get_items(); + $items_by_id = []; + + if (!empty($items)) { + foreach ($items as $item) { + $items_by_id[$item->get_id()] = $item; + } + + if (count($items_by_id) !== count($items)) { + $items_by_id = []; + foreach ($items as $item) { + $items_by_id[$item->get_id(true)] = $item; + } + } + + if (isset($data->data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['feed'][0])) { + $channel = &$data->data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['feed'][0]; + } elseif (isset($data->data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['feed'][0])) { + $channel = &$data->data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['feed'][0]; + } elseif (isset($data->data['child'][\SimplePie\SimplePie::NAMESPACE_RDF]['RDF'][0])) { + $channel = &$data->data['child'][\SimplePie\SimplePie::NAMESPACE_RDF]['RDF'][0]; + } elseif (isset($data->data['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['rss'][0]['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['channel'][0])) { + $channel = &$data->data['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['rss'][0]['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['channel'][0]; + } else { + $channel = null; + } + + if ($channel !== null) { + if (isset($channel['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['entry'])) { + unset($channel['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['entry']); + } + if (isset($channel['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['entry'])) { + unset($channel['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['entry']); + } + if (isset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_10]['item'])) { + unset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_10]['item']); + } + if (isset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_090]['item'])) { + unset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_090]['item']); + } + if (isset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['item'])) { + unset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['item']); + } + } + if (isset($data->data['items'])) { + unset($data->data['items']); + } + if (isset($data->data['ordered_items'])) { + unset($data->data['ordered_items']); + } + } + return [serialize($data->data), $items_by_id]; + } +} + +class_alias('SimplePie\Cache\DB', 'SimplePie_Cache_DB'); + +} + +namespace SimplePie\Cache { + +use InvalidArgumentException; + +/** + * Subset of PSR-16 Cache client for caching data arrays + * + * Only get(), set() and delete() methods are used, + * but not has(), getMultiple(), setMultiple() or deleteMultiple(). + * + * The methods names must be different, but should be compatible to the + * methods of \Psr\SimpleCache\CacheInterface. + * + * @internal + */ +interface DataCache +{ + /** + * Fetches a value from the cache. + * + * Equivalent to \Psr\SimpleCache\CacheInterface::get() + * <code> + * public function get(string $key, mixed $default = null): mixed; + * </code> + * + * @param string $key The unique key of this item in the cache. + * @param mixed $default Default value to return if the key does not exist. + * + * @return array|mixed The value of the item from the cache, or $default in case of cache miss. + * + * @throws InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function get_data(string $key, $default = null); + + /** + * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. + * + * Equivalent to \Psr\SimpleCache\CacheInterface::set() + * <code> + * public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool; + * </code> + * + * @param string $key The key of the item to store. + * @param array<mixed> $value The value of the item to store, must be serializable. + * @param null|int $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function set_data(string $key, array $value, ?int $ttl = null): bool; + + /** + * Delete an item from the cache by its unique key. + * + * Equivalent to \Psr\SimpleCache\CacheInterface::delete() + * <code> + * public function delete(string $key): bool; + * </code> + * + * @param string $key The unique cache key of the item to delete. + * + * @return bool True if the item was successfully removed. False if there was an error. + * + * @throws InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function delete_data(string $key): bool; +} + +} + +namespace SimplePie\Cache { + +/** + * Caches data to the filesystem + * + * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead + */ +class File implements Base +{ + /** + * Location string + * + * @see SimplePie::$cache_location + * @var string + */ + protected $location; + + /** + * Filename + * + * @var string + */ + protected $filename; + + /** + * File extension + * + * @var string + */ + protected $extension; + + /** + * File path + * + * @var string + */ + protected $name; + + /** + * Create a new cache object + * + * @param string $location Location string (from SimplePie::$cache_location) + * @param string $name Unique ID for the cache + * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data + */ + public function __construct(string $location, string $name, $type) + { + $this->location = $location; + $this->filename = $name; + $this->extension = $type; + $this->name = "$this->location/$this->filename.$this->extension"; + } + + /** + * Save data to the cache + * + * @param array<mixed>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property + * @return bool Successfulness + */ + public function save($data) + { + if (file_exists($this->name) && is_writable($this->name) || file_exists($this->location) && is_writable($this->location)) { + if ($data instanceof \SimplePie\SimplePie) { + $data = $data->data; + } + + $data = serialize($data); + return (bool) file_put_contents($this->name, $data); + } + return false; + } + + /** + * Retrieve the data saved to the cache + * + * @return array<mixed>|false Data for SimplePie::$data + */ + public function load() + { + if (file_exists($this->name) && is_readable($this->name)) { + return unserialize((string) file_get_contents($this->name)); + } + return false; + } + + /** + * Retrieve the last modified time for the cache + * + * @return int|false Timestamp + */ + public function mtime() + { + return @filemtime($this->name); + } + + /** + * Set the last modified time to the current time + * + * @return bool Success status + */ + public function touch() + { + return @touch($this->name); + } + + /** + * Remove the cache + * + * @return bool Success status + */ + public function unlink() + { + if (file_exists($this->name)) { + return unlink($this->name); + } + return false; + } +} + +class_alias('SimplePie\Cache\File', 'SimplePie_Cache_File'); + +} + +namespace SimplePie\Cache { + +use Memcache as NativeMemcache; + +/** + * Caches data to memcache + * + * Registered for URLs with the "memcache" protocol + * + * For example, `memcache://localhost:11211/?timeout=3600&prefix=sp_` will + * connect to memcache on `localhost` on port 11211. All tables will be + * prefixed with `sp_` and data will expire after 3600 seconds + * + * @uses Memcache + * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead + */ +class Memcache implements Base +{ + /** + * Memcache instance + * + * @var NativeMemcache + */ + protected $cache; + + /** + * Options + * + * @var array<string, mixed> + */ + protected $options; + + /** + * Cache name + * + * @var string + */ + protected $name; + + /** + * Create a new cache object + * + * @param string $location Location string (from SimplePie::$cache_location) + * @param string $name Unique ID for the cache + * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data + */ + public function __construct(string $location, string $name, $type) + { + $this->options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'extras' => [ + 'timeout' => 3600, // one hour + 'prefix' => 'simplepie_', + ], + ]; + $this->options = array_replace_recursive($this->options, \SimplePie\Cache::parse_URL($location)); + + $this->name = $this->options['extras']['prefix'] . md5("$name:$type"); + + $this->cache = new NativeMemcache(); + $this->cache->addServer($this->options['host'], (int) $this->options['port']); + } + + /** + * Save data to the cache + * + * @param array<mixed>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property + * @return bool Successfulness + */ + public function save($data) + { + if ($data instanceof \SimplePie\SimplePie) { + $data = $data->data; + } + return $this->cache->set($this->name, serialize($data), MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']); + } + + /** + * Retrieve the data saved to the cache + * + * @return array<mixed>|false Data for SimplePie::$data + */ + public function load() + { + $data = $this->cache->get($this->name); + + if ($data !== false) { + return unserialize($data); + } + return false; + } + + /** + * Retrieve the last modified time for the cache + * + * @return int|false Timestamp + */ + public function mtime() + { + $data = $this->cache->get($this->name); + + if ($data !== false) { + // essentially ignore the mtime because Memcache expires on its own + return time(); + } + + return false; + } + + /** + * Set the last modified time to the current time + * + * @return bool Success status + */ + public function touch() + { + $data = $this->cache->get($this->name); + + if ($data !== false) { + return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']); + } + + return false; + } + + /** + * Remove the cache + * + * @return bool Success status + */ + public function unlink() + { + return $this->cache->delete($this->name, 0); + } +} + +class_alias('SimplePie\Cache\Memcache', 'SimplePie_Cache_Memcache'); + +} + +namespace SimplePie\Cache { + +use Memcached as NativeMemcached; + +/** + * Caches data to memcached + * + * Registered for URLs with the "memcached" protocol + * + * For example, `memcached://localhost:11211/?timeout=3600&prefix=sp_` will + * connect to memcached on `localhost` on port 11211. All tables will be + * prefixed with `sp_` and data will expire after 3600 seconds + * + * @uses Memcached + * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead + */ +class Memcached implements Base +{ + /** + * NativeMemcached instance + * @var NativeMemcached + */ + protected $cache; + + /** + * Options + * @var array<string, mixed> + */ + protected $options; + + /** + * Cache name + * @var string + */ + protected $name; + + /** + * Create a new cache object + * @param string $location Location string (from SimplePie::$cache_location) + * @param string $name Unique ID for the cache + * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data + */ + public function __construct(string $location, string $name, $type) + { + $this->options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'extras' => [ + 'timeout' => 3600, // one hour + 'prefix' => 'simplepie_', + ], + ]; + $this->options = array_replace_recursive($this->options, \SimplePie\Cache::parse_URL($location)); + + $this->name = $this->options['extras']['prefix'] . md5("$name:$type"); + + $this->cache = new NativeMemcached(); + $this->cache->addServer($this->options['host'], (int)$this->options['port']); + } + + /** + * Save data to the cache + * @param array<mixed>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property + * @return bool Successfulness + */ + public function save($data) + { + if ($data instanceof \SimplePie\SimplePie) { + $data = $data->data; + } + + return $this->setData(serialize($data)); + } + + /** + * Retrieve the data saved to the cache + * @return array<mixed>|false Data for SimplePie::$data + */ + public function load() + { + $data = $this->cache->get($this->name); + + if ($data !== false) { + return unserialize($data); + } + return false; + } + + /** + * Retrieve the last modified time for the cache + * @return int Timestamp + */ + public function mtime() + { + $data = $this->cache->get($this->name . '_mtime'); + return (int) $data; + } + + /** + * Set the last modified time to the current time + * @return bool Success status + */ + public function touch() + { + $data = $this->cache->get($this->name); + return $this->setData($data); + } + + /** + * Remove the cache + * @return bool Success status + */ + public function unlink() + { + return $this->cache->delete($this->name, 0); + } + + /** + * Set the last modified time and data to NativeMemcached + * @param string|false $data + * @return bool Success status + */ + private function setData($data): bool + { + if ($data !== false) { + $this->cache->set($this->name . '_mtime', time(), (int)$this->options['extras']['timeout']); + return $this->cache->set($this->name, $data, (int)$this->options['extras']['timeout']); + } + + return false; + } +} + +class_alias('SimplePie\Cache\Memcached', 'SimplePie_Cache_Memcached'); + +} + +namespace SimplePie\Cache { + +/** + * Caches data to a MySQL database + * + * Registered for URLs with the "mysql" protocol + * + * For example, `mysql://root:password@localhost:3306/mydb?prefix=sp_` will + * connect to the `mydb` database on `localhost` on port 3306, with the user + * `root` and the password `password`. All tables will be prefixed with `sp_` + * + * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead + */ +class MySQL extends DB +{ + /** + * PDO instance + * + * @var \PDO|null + */ + protected $mysql; + + /** + * Options + * + * @var array<string, mixed> + */ + protected $options; + + /** + * Cache ID + * + * @var string + */ + protected $id; + + /** + * Create a new cache object + * + * @param string $location Location string (from SimplePie::$cache_location) + * @param string $name Unique ID for the cache + * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data + */ + public function __construct(string $location, string $name, $type) + { + $this->options = [ + 'user' => null, + 'pass' => null, + 'host' => '127.0.0.1', + 'port' => '3306', + 'path' => '', + 'extras' => [ + 'prefix' => '', + 'cache_purge_time' => 2592000 + ], + ]; + + $this->options = array_replace_recursive($this->options, \SimplePie\Cache::parse_URL($location)); + + // Path is prefixed with a "/" + $this->options['dbname'] = substr($this->options['path'], 1); + + try { + $this->mysql = new \PDO("mysql:dbname={$this->options['dbname']};host={$this->options['host']};port={$this->options['port']}", $this->options['user'], $this->options['pass'], [\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); + } catch (\PDOException $e) { + $this->mysql = null; + return; + } + + $this->id = $name . $type; + + if (!$query = $this->mysql->query('SHOW TABLES')) { + $this->mysql = null; + return; + } + + $db = []; + while ($row = $query->fetchColumn()) { + $db[] = $row; + } + + if (!in_array($this->options['extras']['prefix'] . 'cache_data', $db)) { + $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))'); + if ($query === false) { + trigger_error("Can't create " . $this->options['extras']['prefix'] . "cache_data table, check permissions", \E_USER_WARNING); + $this->mysql = null; + return; + } + } + + if (!in_array($this->options['extras']['prefix'] . 'items', $db)) { + $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` MEDIUMBLOB NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))'); + if ($query === false) { + trigger_error("Can't create " . $this->options['extras']['prefix'] . "items table, check permissions", \E_USER_WARNING); + $this->mysql = null; + return; + } + } + } + + /** + * Save data to the cache + * + * @param array<string>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property + * @return bool Successfulness + */ + public function save($data) + { + if ($this->mysql === null) { + return false; + } + + $query = $this->mysql->prepare('DELETE i, cd FROM `' . $this->options['extras']['prefix'] . 'cache_data` cd, ' . + '`' . $this->options['extras']['prefix'] . 'items` i ' . + 'WHERE cd.id = i.feed_id ' . + 'AND cd.mtime < (unix_timestamp() - :purge_time)'); + $query->bindValue(':purge_time', $this->options['extras']['cache_purge_time']); + + if (!$query->execute()) { + return false; + } + + if ($data instanceof \SimplePie\SimplePie) { + $data = clone $data; + + $prepared = self::prepare_simplepie_object_for_cache($data); + + $query = $this->mysql->prepare('SELECT COUNT(*) FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed'); + $query->bindValue(':feed', $this->id); + if ($query->execute()) { + if ($query->fetchColumn() > 0) { + $items = count($prepared[1]); + if ($items) { + $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = :items, `data` = :data, `mtime` = :time WHERE `id` = :feed'; + $query = $this->mysql->prepare($sql); + $query->bindValue(':items', $items); + } else { + $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `data` = :data, `mtime` = :time WHERE `id` = :feed'; + $query = $this->mysql->prepare($sql); + } + + $query->bindValue(':data', $prepared[0]); + $query->bindValue(':time', time()); + $query->bindValue(':feed', $this->id); + if (!$query->execute()) { + return false; + } + } else { + $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:feed, :count, :data, :time)'); + $query->bindValue(':feed', $this->id); + $query->bindValue(':count', count($prepared[1])); + $query->bindValue(':data', $prepared[0]); + $query->bindValue(':time', time()); + if (!$query->execute()) { + return false; + } + } + + $ids = array_keys($prepared[1]); + if (!empty($ids)) { + foreach ($ids as $id) { + $database_ids[] = $this->mysql->quote($id); + } + + $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `id` = ' . implode(' OR `id` = ', $database_ids) . ' AND `feed_id` = :feed'); + $query->bindValue(':feed', $this->id); + + if ($query->execute()) { + $existing_ids = []; + while ($row = $query->fetchColumn()) { + $existing_ids[] = $row; + } + + $new_ids = array_diff($ids, $existing_ids); + + foreach ($new_ids as $new_id) { + if (!($date = $prepared[1][$new_id]->get_date('U'))) { + $date = time(); + } + + $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(:feed, :id, :data, :date)'); + $query->bindValue(':feed', $this->id); + $query->bindValue(':id', $new_id); + $query->bindValue(':data', serialize($prepared[1][$new_id]->data)); + $query->bindValue(':date', $date); + if (!$query->execute()) { + return false; + } + } + return true; + } + } else { + return true; + } + } + } else { + $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed'); + $query->bindValue(':feed', $this->id); + if ($query->execute()) { + if ($query->rowCount() > 0) { + $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = 0, `data` = :data, `mtime` = :time WHERE `id` = :feed'); + $query->bindValue(':data', serialize($data)); + $query->bindValue(':time', time()); + $query->bindValue(':feed', $this->id); + if ($query->execute()) { + return true; + } + } else { + $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:id, 0, :data, :time)'); + $query->bindValue(':id', $this->id); + $query->bindValue(':data', serialize($data)); + $query->bindValue(':time', time()); + if ($query->execute()) { + return true; + } + } + } + } + return false; + } + + /** + * Retrieve the data saved to the cache + * + * @return array<string>|false Data for SimplePie::$data + */ + public function load() + { + if ($this->mysql === null) { + return false; + } + + $query = $this->mysql->prepare('SELECT `items`, `data` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); + $query->bindValue(':id', $this->id); + if ($query->execute() && ($row = $query->fetch())) { + $data = unserialize($row[1]); + + if (isset($this->options['items'][0])) { + $items = (int) $this->options['items'][0]; + } else { + $items = (int) $row[0]; + } + + if ($items !== 0) { + if (isset($data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['feed'][0])) { + $feed = &$data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['feed'][0]; + } elseif (isset($data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['feed'][0])) { + $feed = &$data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['feed'][0]; + } elseif (isset($data['child'][\SimplePie\SimplePie::NAMESPACE_RDF]['RDF'][0])) { + $feed = &$data['child'][\SimplePie\SimplePie::NAMESPACE_RDF]['RDF'][0]; + } elseif (isset($data['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['rss'][0])) { + $feed = &$data['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['rss'][0]; + } else { + $feed = null; + } + + if ($feed !== null) { + $sql = 'SELECT `data` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :feed ORDER BY `posted` DESC'; + if ($items > 0) { + $sql .= ' LIMIT ' . $items; + } + + $query = $this->mysql->prepare($sql); + $query->bindValue(':feed', $this->id); + if ($query->execute()) { + while ($row = $query->fetchColumn()) { + $feed['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['entry'][] = unserialize((string) $row); + } + } else { + return false; + } + } + } + return $data; + } + return false; + } + + /** + * Retrieve the last modified time for the cache + * + * @return int|false Timestamp + */ + public function mtime() + { + if ($this->mysql === null) { + return false; + } + + $query = $this->mysql->prepare('SELECT `mtime` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); + $query->bindValue(':id', $this->id); + if ($query->execute() && ($time = $query->fetchColumn())) { + return (int) $time; + } + + return false; + } + + /** + * Set the last modified time to the current time + * + * @return bool Success status + */ + public function touch() + { + if ($this->mysql === null) { + return false; + } + + $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `mtime` = :time WHERE `id` = :id'); + $query->bindValue(':time', time()); + $query->bindValue(':id', $this->id); + + return $query->execute() && $query->rowCount() > 0; + } + + /** + * Remove the cache + * + * @return bool Success status + */ + public function unlink() + { + if ($this->mysql === null) { + return false; + } + + $query = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); + $query->bindValue(':id', $this->id); + $query2 = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :id'); + $query2->bindValue(':id', $this->id); + + return $query->execute() && $query2->execute(); + } +} + +class_alias('SimplePie\Cache\MySQL', 'SimplePie_Cache_MySQL'); + +} + +namespace SimplePie\Cache { + +/** + * Interface for creating a cache filename + */ +interface NameFilter +{ + /** + * Method to create cache filename with. + * + * The returning name MUST follow the rules for keys in PSR-16. + * + * @link https://www.php-fig.org/psr/psr-16/ + * + * The returning name MUST be a string of at least one character + * that uniquely identifies a cached item, MUST only contain the + * characters A-Z, a-z, 0-9, _, and . in any order in UTF-8 encoding + * and MUST not longer then 64 characters. The following characters + * are reserved for future extensions and MUST NOT be used: {}()/\@: + * + * A provided implementing library MAY support additional characters + * and encodings or longer lengths, but MUST support at least that + * minimum. + * + * @param string $name The name for the cache will be most likely an url with query string + * + * @return string the new cache name + */ + public function filter(string $name): string; +} + +} + +namespace SimplePie\Cache { + +use Psr\SimpleCache\CacheInterface; +use Psr\SimpleCache\InvalidArgumentException; +use Throwable; + +/** + * Caches data into a PSR-16 cache implementation + * + * @internal + */ +final class Psr16 implements DataCache +{ + /** + * PSR-16 cache implementation + * + * @var CacheInterface + */ + private $cache; + + /** + * PSR-16 cache implementation + * + * @param CacheInterface $cache + */ + public function __construct(CacheInterface $cache) + { + $this->cache = $cache; + } + + /** + * Fetches a value from the cache. + * + * Equivalent to \Psr\SimpleCache\CacheInterface::get() + * <code> + * public function get(string $key, mixed $default = null): mixed; + * </code> + * + * @param string $key The unique key of this item in the cache. + * @param mixed $default Default value to return if the key does not exist. + * + * @return array|mixed The value of the item from the cache, or $default in case of cache miss. + * + * @throws InvalidArgumentException&Throwable + * MUST be thrown if the $key string is not a legal value. + */ + public function get_data(string $key, $default = null) + { + $data = $this->cache->get($key, $default); + + if (!is_array($data) || $data === $default) { + return $default; + } + + return $data; + } + + /** + * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. + * + * Equivalent to \Psr\SimpleCache\CacheInterface::set() + * <code> + * public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool; + * </code> + * + * @param string $key The key of the item to store. + * @param array<mixed> $value The value of the item to store, must be serializable. + * @param null|int $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws InvalidArgumentException&Throwable + * MUST be thrown if the $key string is not a legal value. + */ + public function set_data(string $key, array $value, ?int $ttl = null): bool + { + return $this->cache->set($key, $value, $ttl); + } + + /** + * Delete an item from the cache by its unique key. + * + * Equivalent to \Psr\SimpleCache\CacheInterface::delete() + * <code> + * public function delete(string $key): bool; + * </code> + * + * @param string $key The unique cache key of the item to delete. + * + * @return bool True if the item was successfully removed. False if there was an error. + * + * @throws InvalidArgumentException&Throwable + * MUST be thrown if the $key string is not a legal value. + */ + public function delete_data(string $key): bool + { + return $this->cache->delete($key); + } +} + +} + +namespace SimplePie\Cache { + +use Redis as NativeRedis; + +/** + * Caches data to redis + * + * Registered for URLs with the "redis" protocol + * + * For example, `redis://localhost:6379/?timeout=3600&prefix=sp_&dbIndex=0` will + * connect to redis on `localhost` on port 6379. All tables will be + * prefixed with `simple_primary-` and data will expire after 3600 seconds + * + * @uses Redis + * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead + */ +class Redis implements Base +{ + /** + * Redis instance + * + * @var NativeRedis + */ + protected $cache; + + /** + * Options + * + * @var array<string, mixed> + */ + protected $options; + + /** + * Cache name + * + * @var string + */ + protected $name; + + /** + * Create a new cache object + * + * @param string $location Location string (from SimplePie::$cache_location) + * @param string $name Unique ID for the cache + * @param Base::TYPE_FEED|Base::TYPE_IMAGE|array<string, mixed>|null $options Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data + */ + public function __construct(string $location, string $name, $options = null) + { + //$this->cache = \flow\simple\cache\Redis::getRedisClientInstance(); + $parsed = \SimplePie\Cache::parse_URL($location); + $redis = new NativeRedis(); + $redis->connect($parsed['host'], $parsed['port']); + if (isset($parsed['pass'])) { + $redis->auth($parsed['pass']); + } + if (isset($parsed['path'])) { + $redis->select((int)substr($parsed['path'], 1)); + } + $this->cache = $redis; + + if (!is_null($options) && is_array($options)) { + $this->options = $options; + } else { + $this->options = [ + 'prefix' => 'rss:simple_primary:', + 'expire' => 0, + ]; + } + + $this->name = $this->options['prefix'] . $name; + } + + /** + * @param NativeRedis $cache + * @return void + */ + public function setRedisClient(NativeRedis $cache) + { + $this->cache = $cache; + } + + /** + * Save data to the cache + * + * @param array<mixed>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property + * @return bool Successfulness + */ + public function save($data) + { + if ($data instanceof \SimplePie\SimplePie) { + $data = $data->data; + } + $response = $this->cache->set($this->name, serialize($data)); + if ($this->options['expire']) { + $this->cache->expire($this->name, $this->options['expire']); + } + + return $response; + } + + /** + * Retrieve the data saved to the cache + * + * @return array<mixed>|false Data for SimplePie::$data + */ + public function load() + { + $data = $this->cache->get($this->name); + + if ($data !== false) { + return unserialize($data); + } + return false; + } + + /** + * Retrieve the last modified time for the cache + * + * @return int|false Timestamp + */ + public function mtime() + { + $data = $this->cache->get($this->name); + + if ($data !== false) { + return time(); + } + + return false; + } + + /** + * Set the last modified time to the current time + * + * @return bool Success status + */ + public function touch() + { + $data = $this->cache->get($this->name); + + if ($data !== false) { + $return = $this->cache->set($this->name, $data); + if ($this->options['expire']) { + return $this->cache->expire($this->name, $this->options['expire']); + } + return $return; + } + + return false; + } + + /** + * Remove the cache + * + * @return bool Success status + */ + public function unlink() + { + return $this->cache->set($this->name, null); + } +} + +class_alias('SimplePie\Cache\Redis', 'SimplePie_Cache_Redis'); + +} + +namespace SimplePie { + +/** + * Handles `<media:text>` captions as defined in Media RSS. + * + * Used by {@see \SimplePie\Enclosure::get_caption()} and {@see \SimplePie\Enclosure::get_captions()} + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_caption_class()} + */ +class Caption +{ + /** + * Content type + * + * @var ?string + * @see get_type() + */ + public $type; + + /** + * Language + * + * @var ?string + * @see get_language() + */ + public $lang; + + /** + * Start time + * + * @var ?string + * @see get_starttime() + */ + public $startTime; + + /** + * End time + * + * @var ?string + * @see get_endtime() + */ + public $endTime; + + /** + * Caption text + * + * @var ?string + * @see get_text() + */ + public $text; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + */ + public function __construct( + ?string $type = null, + ?string $lang = null, + ?string $startTime = null, + ?string $endTime = null, + ?string $text = null + ) { + $this->type = $type; + $this->lang = $lang; + $this->startTime = $startTime; + $this->endTime = $endTime; + $this->text = $text; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the end time + * + * @return string|null Time in the format 'hh:mm:ss.SSS' + */ + public function get_endtime() + { + if ($this->endTime !== null) { + return $this->endTime; + } + + return null; + } + + /** + * Get the language + * + * @link http://tools.ietf.org/html/rfc3066 + * @return string|null Language code as per RFC 3066 + */ + public function get_language() + { + if ($this->lang !== null) { + return $this->lang; + } + + return null; + } + + /** + * Get the start time + * + * @return string|null Time in the format 'hh:mm:ss.SSS' + */ + public function get_starttime() + { + if ($this->startTime !== null) { + return $this->startTime; + } + + return null; + } + + /** + * Get the text of the caption + * + * @return string|null + */ + public function get_text() + { + if ($this->text !== null) { + return $this->text; + } + + return null; + } + + /** + * Get the content type (not MIME type) + * + * @return string|null Either 'text' or 'html' + */ + public function get_type() + { + if ($this->type !== null) { + return $this->type; + } + + return null; + } +} + +class_alias('SimplePie\Caption', 'SimplePie_Caption'); + +} + +namespace SimplePie { + +/** + * Manages all category-related data + * + * Used by {@see \SimplePie\Item::get_category()} and {@see \SimplePie\Item::get_categories()} + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_category_class()} + */ +class Category +{ + /** + * Category identifier + * + * @var string|null + * @see get_term + */ + public $term; + + /** + * Categorization scheme identifier + * + * @var string|null + * @see get_scheme() + */ + public $scheme; + + /** + * Human readable label + * + * @var string|null + * @see get_label() + */ + public $label; + + /** + * Category type + * + * category for <category> + * subject for <dc:subject> + * + * @var string|null + * @see get_type() + */ + public $type; + + /** + * Constructor, used to input the data + * + * @param string|null $term + * @param string|null $scheme + * @param string|null $label + * @param string|null $type + */ + public function __construct(?string $term = null, ?string $scheme = null, ?string $label = null, ?string $type = null) + { + $this->term = $term; + $this->scheme = $scheme; + $this->label = $label; + $this->type = $type; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the category identifier + * + * @return string|null + */ + public function get_term() + { + return $this->term; + } + + /** + * Get the categorization scheme identifier + * + * @return string|null + */ + public function get_scheme() + { + return $this->scheme; + } + + /** + * Get the human readable label + * + * @param bool $strict + * @return string|null + */ + public function get_label(bool $strict = false) + { + if ($this->label === null && $strict !== true) { + return $this->get_term(); + } + return $this->label; + } + + /** + * Get the category type + * + * @return string|null + */ + public function get_type() + { + return $this->type; + } +} + +class_alias('SimplePie\Category', 'SimplePie_Category'); + +} + +namespace SimplePie\Content\Type { + +use InvalidArgumentException; +use SimplePie\File; +use SimplePie\HTTP\Response; + +/** + * Content-type sniffing + * + * Based on the rules in http://tools.ietf.org/html/draft-abarth-mime-sniff-06 + * + * This is used since we can't always trust Content-Type headers, and is based + * upon the HTML5 parsing rules. + * + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_content_type_sniffer_class()} + */ +class Sniffer +{ + /** + * File object + * + * @var File|Response + */ + public $file; + + /** + * Create an instance of the class with the input file + * + * @param File|Response $file Input file + */ + public function __construct(/* File */ $file) + { + if (!is_object($file) || !$file instanceof Response) { + // For BC we're asking for `File`, but internally we accept every `Response` implementation + throw new InvalidArgumentException(sprintf( + '%s(): Argument #1 ($file) must be of type %s', + __METHOD__, + File::class + ), 1); + } + + $this->file = $file; + } + + /** + * Get the Content-Type of the specified file + * + * @return string Actual Content-Type + */ + public function get_type() + { + $content_type = $this->file->has_header('content-type') ? $this->file->get_header_line('content-type') : null; + $content_encoding = $this->file->has_header('content-encoding') ? $this->file->get_header_line('content-encoding') : null; + if ($content_type !== null) { + if ($content_encoding === null + && ($content_type === 'text/plain' + || $content_type === 'text/plain; charset=ISO-8859-1' + || $content_type === 'text/plain; charset=iso-8859-1' + || $content_type === 'text/plain; charset=UTF-8')) { + return $this->text_or_binary(); + } + + if (($pos = strpos($content_type, ';')) !== false) { + $official = substr($content_type, 0, $pos); + } else { + $official = $content_type; + } + $official = trim(strtolower($official)); + + if ($official === 'unknown/unknown' + || $official === 'application/unknown') { + return $this->unknown(); + } elseif (substr($official, -4) === '+xml' + || $official === 'text/xml' + || $official === 'application/xml') { + return $official; + } elseif (substr($official, 0, 6) === 'image/') { + if ($return = $this->image()) { + return $return; + } + + return $official; + } elseif ($official === 'text/html') { + return $this->feed_or_html(); + } + + return $official; + } + + return $this->unknown(); + } + + /** + * Sniff text or binary + * + * @return string Actual Content-Type + */ + public function text_or_binary() + { + $body = $this->file->get_body_content(); + + if (substr($body, 0, 2) === "\xFE\xFF" + || substr($body, 0, 2) === "\xFF\xFE" + || substr($body, 0, 4) === "\x00\x00\xFE\xFF" + || substr($body, 0, 3) === "\xEF\xBB\xBF") { + return 'text/plain'; + } elseif (preg_match('/[\x00-\x08\x0E-\x1A\x1C-\x1F]/', $body)) { + return 'application/octet-stream'; + } + + return 'text/plain'; + } + + /** + * Sniff unknown + * + * @return string Actual Content-Type + */ + public function unknown() + { + $body = $this->file->get_body_content(); + + $ws = strspn($body, "\x09\x0A\x0B\x0C\x0D\x20"); + if (strtolower(substr($body, $ws, 14)) === '<!doctype html' + || strtolower(substr($body, $ws, 5)) === '<html' + || strtolower(substr($body, $ws, 7)) === '<script') { + return 'text/html'; + } elseif (substr($body, 0, 5) === '%PDF-') { + return 'application/pdf'; + } elseif (substr($body, 0, 11) === '%!PS-Adobe-') { + return 'application/postscript'; + } elseif (substr($body, 0, 6) === 'GIF87a' + || substr($body, 0, 6) === 'GIF89a') { + return 'image/gif'; + } elseif (substr($body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") { + return 'image/png'; + } elseif (substr($body, 0, 3) === "\xFF\xD8\xFF") { + return 'image/jpeg'; + } elseif (substr($body, 0, 2) === "\x42\x4D") { + return 'image/bmp'; + } elseif (substr($body, 0, 4) === "\x00\x00\x01\x00") { + return 'image/vnd.microsoft.icon'; + } + + return $this->text_or_binary(); + } + + /** + * Sniff images + * + * @return string|false Actual Content-Type + */ + public function image() + { + $body = $this->file->get_body_content(); + + if (substr($body, 0, 6) === 'GIF87a' + || substr($body, 0, 6) === 'GIF89a') { + return 'image/gif'; + } elseif (substr($body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") { + return 'image/png'; + } elseif (substr($body, 0, 3) === "\xFF\xD8\xFF") { + return 'image/jpeg'; + } elseif (substr($body, 0, 2) === "\x42\x4D") { + return 'image/bmp'; + } elseif (substr($body, 0, 4) === "\x00\x00\x01\x00") { + return 'image/vnd.microsoft.icon'; + } + + return false; + } + + /** + * Sniff HTML + * + * @return string Actual Content-Type + */ + public function feed_or_html() + { + $body = $this->file->get_body_content(); + + $len = strlen($body); + $pos = strspn($body, "\x09\x0A\x0D\x20\xEF\xBB\xBF"); + + while ($pos < $len) { + switch ($body[$pos]) { + case "\x09": + case "\x0A": + case "\x0D": + case "\x20": + $pos += strspn($body, "\x09\x0A\x0D\x20", $pos); + continue 2; + + case '<': + $pos++; + break; + + default: + return 'text/html'; + } + + if (substr($body, $pos, 3) === '!--') { + $pos += 3; + if ($pos < $len && ($pos = strpos($body, '-->', $pos)) !== false) { + $pos += 3; + } else { + return 'text/html'; + } + } elseif (substr($body, $pos, 1) === '!') { + if ($pos < $len && ($pos = strpos($body, '>', $pos)) !== false) { + $pos++; + } else { + return 'text/html'; + } + } elseif (substr($body, $pos, 1) === '?') { + if ($pos < $len && ($pos = strpos($body, '?>', $pos)) !== false) { + $pos += 2; + } else { + return 'text/html'; + } + } elseif (substr($body, $pos, 3) === 'rss' + || substr($body, $pos, 7) === 'rdf:RDF') { + return 'application/rss+xml'; + } elseif (substr($body, $pos, 4) === 'feed') { + return 'application/atom+xml'; + } else { + return 'text/html'; + } + } + + return 'text/html'; + } +} + +class_alias('SimplePie\Content\Type\Sniffer', 'SimplePie_Content_Type_Sniffer'); + +} + +namespace SimplePie { + +/** + * Manages `<media:copyright>` copyright tags as defined in Media RSS + * + * Used by {@see \SimplePie\Enclosure::get_copyright()} + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_copyright_class()} + */ +class Copyright +{ + /** + * Copyright URL + * + * @var ?string + * @see get_url() + */ + public $url; + + /** + * Attribution + * + * @var ?string + * @see get_attribution() + */ + public $label; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + */ + public function __construct( + ?string $url = null, + ?string $label = null + ) { + $this->url = $url; + $this->label = $label; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the copyright URL + * + * @return string|null URL to copyright information + */ + public function get_url() + { + if ($this->url !== null) { + return $this->url; + } + + return null; + } + + /** + * Get the attribution text + * + * @return string|null + */ + public function get_attribution() + { + if ($this->label !== null) { + return $this->label; + } + + return null; + } +} + +class_alias('SimplePie\Copyright', 'SimplePie_Copyright'); + +} + +namespace SimplePie { + +/** + * Handles `<media:credit>` as defined in Media RSS + * + * Used by {@see \SimplePie\Enclosure::get_credit()} and {@see \SimplePie\Enclosure::get_credits()} + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_credit_class()} + */ +class Credit +{ + /** + * Credited role + * + * @var ?string + * @see get_role() + */ + public $role; + + /** + * Organizational scheme + * + * @var ?string + * @see get_scheme() + */ + public $scheme; + + /** + * Credited name + * + * @var ?string + * @see get_name() + */ + public $name; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + */ + public function __construct( + ?string $role = null, + ?string $scheme = null, + ?string $name = null + ) { + $this->role = $role; + $this->scheme = $scheme; + $this->name = $name; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the role of the person receiving credit + * + * @return string|null + */ + public function get_role() + { + if ($this->role !== null) { + return $this->role; + } + + return null; + } + + /** + * Get the organizational scheme + * + * @return string|null + */ + public function get_scheme() + { + if ($this->scheme !== null) { + return $this->scheme; + } + + return null; + } + + /** + * Get the credited person/entity's name + * + * @return string|null + */ + public function get_name() + { + if ($this->name !== null) { + return $this->name; + } + + return null; + } +} + +class_alias('SimplePie\Credit', 'SimplePie_Credit'); + +} + +namespace SimplePie { + +/** + * Handles everything related to enclosures (including Media RSS and iTunes RSS) + * + * Used by {@see \SimplePie\Item::get_enclosure()} and {@see \SimplePie\Item::get_enclosures()} + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_enclosure_class()} + */ +class Enclosure +{ + /** + * @var ?string + * @see get_bitrate() + */ + public $bitrate; + + /** + * @var Caption[]|null + * @see get_captions() + */ + public $captions; + + /** + * @var Category[]|null + * @see get_categories() + */ + public $categories; + + /** + * @var ?int + * @see get_channels() + */ + public $channels; + + /** + * @var ?Copyright + * @see get_copyright() + */ + public $copyright; + + /** + * @var Credit[]|null + * @see get_credits() + */ + public $credits; + + /** + * @var ?string + * @see get_description() + */ + public $description; + + /** + * @var ?int + * @see get_duration() + */ + public $duration; + + /** + * @var ?string + * @see get_expression() + */ + public $expression; + + /** + * @var ?string + * @see get_framerate() + */ + public $framerate; + + /** + * @var ?string + * @see get_handler() + */ + public $handler; + + /** + * @var string[]|null + * @see get_hashes() + */ + public $hashes; + + /** + * @var ?string + * @see get_height() + */ + public $height; + + /** + * @deprecated + * @var null + */ + public $javascript; + + /** + * @var string[]|null + * @see get_keywords() + */ + public $keywords; + + /** + * @var ?string + * @see get_language() + */ + public $lang; + + /** + * @var ?int + * @see get_length() + */ + public $length; + + /** + * @var ?string + * @see get_link() + */ + public $link; + + /** + * @var ?string + * @see get_medium() + */ + public $medium; + + /** + * @var ?string + * @see get_player() + */ + public $player; + + /** + * @var Rating[]|null + * @see get_ratings() + */ + public $ratings; + + /** + * @var ?Restriction[] + * @see get_restrictions() + */ + public $restrictions; + + /** + * @var ?string + * @see get_sampling_rate() + */ + public $samplingrate; + + /** + * @var string[]|null + * @see get_thumbnails() + */ + public $thumbnails; + + /** + * @var ?string + * @see get_title() + */ + public $title; + + /** + * @var ?string + * @see get_type() + */ + public $type; + + /** + * @var ?string + * @see get_width() + */ + public $width; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + * + * @uses idn_to_ascii If available, this will convert an IDN + * + * @param null $javascript + * @param Caption[]|null $captions + * @param Category[]|null $categories + * @param Credit[]|null $credits + * @param string[]|null $hashes + * @param string[]|null $keywords + * @param Rating[]|null $ratings + * @param Restriction[]|null $restrictions + * @param string[]|null $thumbnails + */ + public function __construct( + ?string $link = null, + ?string $type = null, + ?int $length = null, + $javascript = null, + ?string $bitrate = null, + ?array $captions = null, + ?array $categories = null, + ?int $channels = null, + ?Copyright $copyright = null, + ?array $credits = null, + ?string $description = null, + ?int $duration = null, + ?string $expression = null, + ?string $framerate = null, + ?array $hashes = null, + ?string $height = null, + ?array $keywords = null, + ?string $lang = null, + ?string $medium = null, + ?string $player = null, + ?array $ratings = null, + ?array $restrictions = null, + ?string $samplingrate = null, + ?array $thumbnails = null, + ?string $title = null, + ?string $width = null + ) { + $this->bitrate = $bitrate; + $this->captions = $captions; + $this->categories = $categories; + $this->channels = $channels; + $this->copyright = $copyright; + $this->credits = $credits; + $this->description = $description; + $this->duration = $duration; + $this->expression = $expression; + $this->framerate = $framerate; + $this->hashes = $hashes; + $this->height = $height; + $this->keywords = $keywords; + $this->lang = $lang; + $this->length = $length; + $this->link = $link; + $this->medium = $medium; + $this->player = $player; + $this->ratings = $ratings; + $this->restrictions = $restrictions; + $this->samplingrate = $samplingrate; + $this->thumbnails = $thumbnails; + $this->title = $title; + $this->type = $type; + $this->width = $width; + + if (function_exists('idn_to_ascii')) { + $parsed = \SimplePie\Misc::parse_url($link ?? ''); + if ($parsed['authority'] !== '' && !ctype_print($parsed['authority'])) { + $authority = (string) \idn_to_ascii($parsed['authority'], \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46); + $this->link = \SimplePie\Misc::compress_parse_url($parsed['scheme'], $authority, $parsed['path'], $parsed['query'], $parsed['fragment']); + } + } + $this->handler = $this->get_handler(); // Needs to load last + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the bitrate + * + * @return string|null + */ + public function get_bitrate() + { + if ($this->bitrate !== null) { + return $this->bitrate; + } + + return null; + } + + /** + * Get a single caption + * + * @param int $key + * @return \SimplePie\Caption|null + */ + public function get_caption(int $key = 0) + { + $captions = $this->get_captions(); + if (isset($captions[$key])) { + return $captions[$key]; + } + + return null; + } + + /** + * Get all captions + * + * @return Caption[]|null + */ + public function get_captions() + { + if ($this->captions !== null) { + return $this->captions; + } + + return null; + } + + /** + * Get a single category + * + * @param int $key + * @return \SimplePie\Category|null + */ + public function get_category(int $key = 0) + { + $categories = $this->get_categories(); + if (isset($categories[$key])) { + return $categories[$key]; + } + + return null; + } + + /** + * Get all categories + * + * @return \SimplePie\Category[]|null + */ + public function get_categories() + { + if ($this->categories !== null) { + return $this->categories; + } + + return null; + } + + /** + * Get the number of audio channels + * + * @return int|null + */ + public function get_channels() + { + if ($this->channels !== null) { + return $this->channels; + } + + return null; + } + + /** + * Get the copyright information + * + * @return \SimplePie\Copyright|null + */ + public function get_copyright() + { + if ($this->copyright !== null) { + return $this->copyright; + } + + return null; + } + + /** + * Get a single credit + * + * @param int $key + * @return \SimplePie\Credit|null + */ + public function get_credit(int $key = 0) + { + $credits = $this->get_credits(); + if (isset($credits[$key])) { + return $credits[$key]; + } + + return null; + } + + /** + * Get all credits + * + * @return Credit[]|null + */ + public function get_credits() + { + if ($this->credits !== null) { + return $this->credits; + } + + return null; + } + + /** + * Get the description of the enclosure + * + * @return string|null + */ + public function get_description() + { + if ($this->description !== null) { + return $this->description; + } + + return null; + } + + /** + * Get the duration of the enclosure + * + * @param bool $convert Convert seconds into hh:mm:ss + * @return string|int|null 'hh:mm:ss' string if `$convert` was specified, otherwise integer (or null if none found) + */ + public function get_duration(bool $convert = false) + { + if ($this->duration !== null) { + if ($convert) { + $time = \SimplePie\Misc::time_hms($this->duration); + return $time; + } + + return $this->duration; + } + + return null; + } + + /** + * Get the expression + * + * @return string Probably one of 'sample', 'full', 'nonstop', 'clip'. Defaults to 'full' + */ + public function get_expression() + { + if ($this->expression !== null) { + return $this->expression; + } + + return 'full'; + } + + /** + * Get the file extension + * + * @return string|null + */ + public function get_extension() + { + if ($this->link !== null) { + $url = \SimplePie\Misc::parse_url($this->link); + if ($url['path'] !== '') { + return pathinfo($url['path'], PATHINFO_EXTENSION); + } + } + return null; + } + + /** + * Get the framerate (in frames-per-second) + * + * @return string|null + */ + public function get_framerate() + { + if ($this->framerate !== null) { + return $this->framerate; + } + + return null; + } + + /** + * Get the preferred handler + * + * @return string|null One of 'flash', 'fmedia', 'quicktime', 'wmedia', 'mp3' + */ + public function get_handler() + { + return $this->get_real_type(true); + } + + /** + * Get a single hash + * + * @link http://www.rssboard.org/media-rss#media-hash + * @param int $key + * @return string|null Hash as per `media:hash`, prefixed with "$algo:" + */ + public function get_hash(int $key = 0) + { + $hashes = $this->get_hashes(); + if (isset($hashes[$key])) { + return $hashes[$key]; + } + + return null; + } + + /** + * Get all credits + * + * @return string[]|null Array of strings, see {@see get_hash()} + */ + public function get_hashes() + { + if ($this->hashes !== null) { + return $this->hashes; + } + + return null; + } + + /** + * Get the height + * + * @return string|null + */ + public function get_height() + { + if ($this->height !== null) { + return $this->height; + } + + return null; + } + + /** + * Get the language + * + * @link http://tools.ietf.org/html/rfc3066 + * @return string|null Language code as per RFC 3066 + */ + public function get_language() + { + if ($this->lang !== null) { + return $this->lang; + } + + return null; + } + + /** + * Get a single keyword + * + * @param int $key + * @return string|null + */ + public function get_keyword(int $key = 0) + { + $keywords = $this->get_keywords(); + if (isset($keywords[$key])) { + return $keywords[$key]; + } + + return null; + } + + /** + * Get all keywords + * + * @return string[]|null + */ + public function get_keywords() + { + if ($this->keywords !== null) { + return $this->keywords; + } + + return null; + } + + /** + * Get length + * + * @return ?int Length in bytes + */ + public function get_length() + { + if ($this->length !== null) { + return $this->length; + } + + return null; + } + + /** + * Get the URL + * + * @return string|null + */ + public function get_link() + { + if ($this->link !== null) { + return $this->link; + } + + return null; + } + + /** + * Get the medium + * + * @link http://www.rssboard.org/media-rss#media-content + * @return string|null Should be one of 'image', 'audio', 'video', 'document', 'executable' + */ + public function get_medium() + { + if ($this->medium !== null) { + return $this->medium; + } + + return null; + } + + /** + * Get the player URL + * + * Typically the same as {@see get_permalink()} + * @return string|null Player URL + */ + public function get_player() + { + if ($this->player !== null) { + return $this->player; + } + + return null; + } + + /** + * Get a single rating + * + * @param int $key + * @return \SimplePie\Rating|null + */ + public function get_rating(int $key = 0) + { + $ratings = $this->get_ratings(); + if (isset($ratings[$key])) { + return $ratings[$key]; + } + + return null; + } + + /** + * Get all ratings + * + * @return Rating[]|null + */ + public function get_ratings() + { + if ($this->ratings !== null) { + return $this->ratings; + } + + return null; + } + + /** + * Get a single restriction + * + * @param int $key + * @return \SimplePie\Restriction|null + */ + public function get_restriction(int $key = 0) + { + $restrictions = $this->get_restrictions(); + if (isset($restrictions[$key])) { + return $restrictions[$key]; + } + + return null; + } + + /** + * Get all restrictions + * + * @return Restriction[]|null + */ + public function get_restrictions() + { + if ($this->restrictions !== null) { + return $this->restrictions; + } + + return null; + } + + /** + * Get the sampling rate (in kHz) + * + * @return string|null + */ + public function get_sampling_rate() + { + if ($this->samplingrate !== null) { + return $this->samplingrate; + } + + return null; + } + + /** + * Get the file size (in MiB) + * + * @return float|null File size in mebibytes (1048 bytes) + */ + public function get_size() + { + $length = $this->get_length(); + if ($length !== null) { + return round($length / 1048576, 2); + } + + return null; + } + + /** + * Get a single thumbnail + * + * @param int $key + * @return string|null Thumbnail URL + */ + public function get_thumbnail(int $key = 0) + { + $thumbnails = $this->get_thumbnails(); + if (isset($thumbnails[$key])) { + return $thumbnails[$key]; + } + + return null; + } + + /** + * Get all thumbnails + * + * @return string[]|null Array of thumbnail URLs + */ + public function get_thumbnails() + { + if ($this->thumbnails !== null) { + return $this->thumbnails; + } + + return null; + } + + /** + * Get the title + * + * @return string|null + */ + public function get_title() + { + if ($this->title !== null) { + return $this->title; + } + + return null; + } + + /** + * Get mimetype of the enclosure + * + * @see get_real_type() + * @return string|null MIME type + */ + public function get_type() + { + if ($this->type !== null) { + return $this->type; + } + + return null; + } + + /** + * Get the width + * + * @return string|null + */ + public function get_width() + { + if ($this->width !== null) { + return $this->width; + } + + return null; + } + + /** + * Embed the enclosure using `<embed>` + * + * @deprecated Use the second parameter to {@see embed} instead + * + * @param array<string, mixed>|string $options See first parameter to {@see embed} + * @return string HTML string to output + */ + public function native_embed($options = '') + { + return $this->embed($options, true); + } + + /** + * Embed the enclosure using Javascript + * + * `$options` is an array or comma-separated key:value string, with the + * following properties: + * + * - `alt` (string): Alternate content for when an end-user does not have + * the appropriate handler installed or when a file type is + * unsupported. Can be any text or HTML. Defaults to blank. + * - `altclass` (string): If a file type is unsupported, the end-user will + * see the alt text (above) linked directly to the content. That link + * will have this value as its class name. Defaults to blank. + * - `audio` (string): This is an image that should be used as a + * placeholder for audio files before they're loaded (QuickTime-only). + * Can be any relative or absolute URL. Defaults to blank. + * - `bgcolor` (string): The background color for the media, if not + * already transparent. Defaults to `#ffffff`. + * - `height` (integer): The height of the embedded media. Accepts any + * numeric pixel value (such as `360`) or `auto`. Defaults to `auto`, + * and it is recommended that you use this default. + * - `loop` (boolean): Do you want the media to loop when it's done? + * Defaults to `false`. + * - `mediaplayer` (string): The location of the included + * `mediaplayer.swf` file. This allows for the playback of Flash Video + * (`.flv`) files, and is the default handler for non-Odeo MP3's. + * Defaults to blank. + * - `video` (string): This is an image that should be used as a + * placeholder for video files before they're loaded (QuickTime-only). + * Can be any relative or absolute URL. Defaults to blank. + * - `width` (integer): The width of the embedded media. Accepts any + * numeric pixel value (such as `480`) or `auto`. Defaults to `auto`, + * and it is recommended that you use this default. + * - `widescreen` (boolean): Is the enclosure widescreen or standard? + * This applies only to video enclosures, and will automatically resize + * the content appropriately. Defaults to `false`, implying 4:3 mode. + * + * Note: Non-widescreen (4:3) mode with `width` and `height` set to `auto` + * will default to 480x360 video resolution. Widescreen (16:9) mode with + * `width` and `height` set to `auto` will default to 480x270 video resolution. + * + * @todo If the dimensions for media:content are defined, use them when width/height are set to 'auto'. + * @param array<string, mixed>|string $options Comma-separated key:value list, or array + * @param bool $native Use `<embed>` + * @return string HTML string to output + */ + public function embed($options = '', bool $native = false) + { + // Set up defaults + $audio = ''; + $video = ''; + $alt = ''; + $altclass = ''; + $loop = 'false'; + $width = 'auto'; + $height = 'auto'; + $bgcolor = '#ffffff'; + $mediaplayer = ''; + $widescreen = false; + $handler = $this->get_handler(); + $type = $this->get_real_type(); + $placeholder = ''; + + // Process options and reassign values as necessary + if (is_array($options)) { + extract($options); + } else { + $options = explode(',', $options); + foreach ($options as $option) { + $opt = explode(':', $option, 2); + if (isset($opt[0], $opt[1])) { + $opt[0] = trim($opt[0]); + $opt[1] = trim($opt[1]); + switch ($opt[0]) { + case 'audio': + $audio = $opt[1]; + break; + + case 'video': + $video = $opt[1]; + break; + + case 'alt': + $alt = $opt[1]; + break; + + case 'altclass': + $altclass = $opt[1]; + break; + + case 'loop': + $loop = $opt[1]; + break; + + case 'width': + $width = $opt[1]; + break; + + case 'height': + $height = $opt[1]; + break; + + case 'bgcolor': + $bgcolor = $opt[1]; + break; + + case 'mediaplayer': + $mediaplayer = $opt[1]; + break; + + case 'widescreen': + $widescreen = $opt[1]; + break; + } + } + } + } + + $mime = explode('/', (string) $type, 2); + $mime = $mime[0]; + + // Process values for 'auto' + if ($width === 'auto') { + if ($mime === 'video') { + if ($height === 'auto') { + $width = 480; + } elseif ($widescreen) { + $width = round((intval($height) / 9) * 16); + } else { + $width = round((intval($height) / 3) * 4); + } + } else { + $width = '100%'; + } + } + + if ($height === 'auto') { + if ($mime === 'audio') { + $height = 0; + } elseif ($mime === 'video') { + if ($width === 'auto') { + if ($widescreen) { + $height = 270; + } else { + $height = 360; + } + } elseif ($widescreen) { + $height = round((intval($width) / 16) * 9); + } else { + $height = round((intval($width) / 4) * 3); + } + } else { + $height = 376; + } + } elseif ($mime === 'audio') { + $height = 0; + } + + // Set proper placeholder value + if ($mime === 'audio') { + $placeholder = $audio; + } elseif ($mime === 'video') { + $placeholder = $video; + } + + $embed = ''; + + // Flash + if ($handler === 'flash') { + if ($native) { + $embed .= "<embed src=\"" . $this->get_link() . "\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"$type\" quality=\"high\" width=\"$width\" height=\"$height\" bgcolor=\"$bgcolor\" loop=\"$loop\"></embed>"; + } else { + $embed .= "<script type='text/javascript'>embed_flash('$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$loop', '$type');</script>"; + } + } + + // Flash Media Player file types. + // Preferred handler for MP3 file types. + elseif ($handler === 'fmedia' || ($handler === 'mp3' && $mediaplayer !== '')) { + if (is_numeric($height)) { + $height += 20; + } + + if ($native) { + $embed .= "<embed src=\"$mediaplayer\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" quality=\"high\" width=\"$width\" height=\"$height\" wmode=\"transparent\" flashvars=\"file=" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "&autostart=false&repeat=$loop&showdigits=true&showfsbutton=false\"></embed>"; + } else { + $embed .= "<script type='text/javascript'>embed_flv('$width', '$height', '" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "', '$placeholder', '$loop', '$mediaplayer');</script>"; + } + } + + // QuickTime 7 file types. Need to test with QuickTime 6. + // Only handle MP3's if the Flash Media Player is not present. + elseif ($handler === 'quicktime' || ($handler === 'mp3' && $mediaplayer === '')) { + if (is_numeric($height)) { + $height += 16; + } + + if ($native) { + if ($placeholder !== '') { + $embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" href=\"" . $this->get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>"; + } else { + $embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" src=\"" . $this->get_link() . "\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"true\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>"; + } + } else { + $embed .= "<script type='text/javascript'>embed_quicktime('$type', '$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$placeholder', '$loop');</script>"; + } + } + + // Windows Media + elseif ($handler === 'wmedia') { + if (is_numeric($height)) { + $height += 45; + } + + if ($native) { + $embed .= "<embed type=\"application/x-mplayer2\" src=\"" . $this->get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\"></embed>"; + } else { + $embed .= "<script type='text/javascript'>embed_wmedia('$width', '$height', '" . $this->get_link() . "');</script>"; + } + } + + // Everything else + else { + $embed .= '<a href="' . $this->get_link() . '" class="' . $altclass . '">' . $alt . '</a>'; + } + + return $embed; + } + + /** + * Get the real media type + * + * Often, feeds lie to us, necessitating a bit of deeper inspection. This + * converts types to their canonical representations based on the file + * extension + * + * @see get_type() + * @param bool $find_handler Internal use only, use {@see get_handler()} instead + * @return string|null MIME type + */ + public function get_real_type(bool $find_handler = false) + { + // Mime-types by handler. + $types_flash = ['application/x-shockwave-flash', 'application/futuresplash']; // Flash + $types_fmedia = ['video/flv', 'video/x-flv','flv-application/octet-stream']; // Flash Media Player + $types_quicktime = ['audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video']; // QuickTime + $types_wmedia = ['application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx']; // Windows Media + $types_mp3 = ['audio/mp3', 'audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg']; // MP3 + + $type = $this->get_type(); + if ($type !== null) { + $type = strtolower($type); + } + + // If we encounter an unsupported mime-type, check the file extension and guess intelligently. + if (!in_array($type, array_merge($types_flash, $types_fmedia, $types_quicktime, $types_wmedia, $types_mp3))) { + $extension = $this->get_extension(); + if ($extension === null) { + return null; + } + + switch (strtolower($extension)) { + // Audio mime-types + case 'aac': + case 'adts': + $type = 'audio/acc'; + break; + + case 'aif': + case 'aifc': + case 'aiff': + case 'cdda': + $type = 'audio/aiff'; + break; + + case 'bwf': + $type = 'audio/wav'; + break; + + case 'kar': + case 'mid': + case 'midi': + case 'smf': + $type = 'audio/midi'; + break; + + case 'm4a': + $type = 'audio/x-m4a'; + break; + + case 'mp3': + case 'swa': + $type = 'audio/mp3'; + break; + + case 'wav': + $type = 'audio/wav'; + break; + + case 'wax': + $type = 'audio/x-ms-wax'; + break; + + case 'wma': + $type = 'audio/x-ms-wma'; + break; + + case '3gp': + case '3gpp': + // Video mime-types + $type = 'video/3gpp'; + break; + + case '3g2': + case '3gp2': + $type = 'video/3gpp2'; + break; + + case 'asf': + $type = 'video/x-ms-asf'; + break; + + case 'flv': + $type = 'video/x-flv'; + break; + + case 'm1a': + case 'm1s': + case 'm1v': + case 'm15': + case 'm75': + case 'mp2': + case 'mpa': + case 'mpeg': + case 'mpg': + case 'mpm': + case 'mpv': + $type = 'video/mpeg'; + break; + + case 'm4v': + $type = 'video/x-m4v'; + break; + + case 'mov': + case 'qt': + $type = 'video/quicktime'; + break; + + case 'mp4': + case 'mpg4': + $type = 'video/mp4'; + break; + + case 'sdv': + $type = 'video/sd-video'; + break; + + case 'wm': + $type = 'video/x-ms-wm'; + break; + + case 'wmv': + $type = 'video/x-ms-wmv'; + break; + + case 'wvx': + $type = 'video/x-ms-wvx'; + break; + + case 'spl': + // Flash mime-types + $type = 'application/futuresplash'; + break; + + case 'swf': + $type = 'application/x-shockwave-flash'; + break; + } + } + + if ($find_handler) { + if (in_array($type, $types_flash)) { + return 'flash'; + } elseif (in_array($type, $types_fmedia)) { + return 'fmedia'; + } elseif (in_array($type, $types_quicktime)) { + return 'quicktime'; + } elseif (in_array($type, $types_wmedia)) { + return 'wmedia'; + } elseif (in_array($type, $types_mp3)) { + return 'mp3'; + } + + return null; + } + + return $type; + } +} + +class_alias('SimplePie\Enclosure', 'SimplePie_Enclosure'); + +} + +namespace SimplePie { + +use Exception as NativeException; + +/** + * General SimplePie exception class + */ +class Exception extends NativeException +{ +} + +class_alias('SimplePie\Exception', 'SimplePie_Exception'); + +} + +namespace SimplePie { + +use SimplePie\HTTP\Response; + +/** + * Used for fetching remote files and reading local files + * + * Supports HTTP 1.0 via cURL or fsockopen, with spotty HTTP 1.1 support + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_file_class()} + * + * @todo Move to properly supporting RFC2616 (HTTP/1.1) + */ +class File implements Response +{ + /** + * @var string The final URL after following all redirects + * @deprecated Use `get_final_requested_uri()` method. + */ + public $url; + + /** + * @var ?string User agent to use in requests + * @deprecated Set the user agent in constructor. + */ + public $useragent; + + /** @var bool */ + public $success = true; + + /** @var array<string, non-empty-array<string>> Canonical representation of headers */ + private $parsed_headers = []; + /** @var array<string, string> Last known value of $headers property (used to detect external modification) */ + private $last_headers = []; + /** + * @var array<string, string> Headers as string for BC + * @deprecated Use `get_headers()` method. + */ + public $headers = []; + + /** + * @var ?string Body of the HTTP response + * @deprecated Use `get_body_content()` method. + */ + public $body; + + /** + * @var int Status code of the HTTP response + * @deprecated Use `get_status_code()` method. + */ + public $status_code = 0; + + /** @var non-negative-int Number of redirect that were already performed during this request sequence. */ + public $redirects = 0; + + /** @var ?string */ + public $error; + + /** + * @var int-mask-of<SimplePie::FILE_SOURCE_*> Bit mask representing the method used to fetch the file and whether it is a local file or remote file obtained over HTTP. + * @deprecated Backend is implementation detail which you should not care about; to see if the file was retrieved over HTTP, check if `get_final_requested_uri()` with `Misc::is_remote_uri()`. + */ + public $method = \SimplePie\SimplePie::FILE_SOURCE_NONE; + + /** + * @var string The permanent URL or the resource (first URL after the prefix of (only) permanent redirects) + * @deprecated Use `get_permanent_uri()` method. + */ + public $permanent_url; + /** @var bool Whether the permanent URL is still writeable (prefix of permanent redirects has not ended) */ + private $permanentUrlMutable = true; + + /** + * @param string $url + * @param int $timeout + * @param int $redirects + * @param ?array<string, string> $headers + * @param ?string $useragent + * @param bool $force_fsockopen + * @param array<int, mixed> $curl_options + */ + public function __construct(string $url, int $timeout = 10, int $redirects = 5, ?array $headers = null, ?string $useragent = null, bool $force_fsockopen = false, array $curl_options = []) + { + if (function_exists('idn_to_ascii')) { + $parsed = \SimplePie\Misc::parse_url($url); + if ($parsed['authority'] !== '' && !ctype_print($parsed['authority'])) { + $authority = (string) \idn_to_ascii($parsed['authority'], \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46); + $url = \SimplePie\Misc::compress_parse_url($parsed['scheme'], $authority, $parsed['path'], $parsed['query'], null); + } + } + $this->url = $url; + if ($this->permanentUrlMutable) { + $this->permanent_url = $url; + } + $this->useragent = $useragent; + if (preg_match('/^http(s)?:\/\//i', $url)) { + if ($useragent === null) { + $useragent = (string) ini_get('user_agent'); + $this->useragent = $useragent; + } + if (!is_array($headers)) { + $headers = []; + } + if (!$force_fsockopen && function_exists('curl_exec')) { + $this->method = \SimplePie\SimplePie::FILE_SOURCE_REMOTE | \SimplePie\SimplePie::FILE_SOURCE_CURL; + $fp = curl_init(); + $headers2 = []; + foreach ($headers as $key => $value) { + $headers2[] = "$key: $value"; + } + if (isset($curl_options[CURLOPT_HTTPHEADER])) { + if (is_array($curl_options[CURLOPT_HTTPHEADER])) { + $headers2 = array_merge($headers2, $curl_options[CURLOPT_HTTPHEADER]); + } + unset($curl_options[CURLOPT_HTTPHEADER]); + } + if (version_compare(\SimplePie\Misc::get_curl_version(), '7.10.5', '>=')) { + curl_setopt($fp, CURLOPT_ENCODING, ''); + } + curl_setopt($fp, CURLOPT_URL, $url); + curl_setopt($fp, CURLOPT_HEADER, 1); + curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($fp, CURLOPT_FAILONERROR, 1); + curl_setopt($fp, CURLOPT_TIMEOUT, $timeout); + curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout); + curl_setopt($fp, CURLOPT_REFERER, \SimplePie\Misc::url_remove_credentials($url)); + curl_setopt($fp, CURLOPT_USERAGENT, $useragent); + curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); + foreach ($curl_options as $curl_param => $curl_value) { + curl_setopt($fp, $curl_param, $curl_value); + } + + $responseHeaders = curl_exec($fp); + if (curl_errno($fp) === CURLE_WRITE_ERROR || curl_errno($fp) === CURLE_BAD_CONTENT_ENCODING) { + curl_setopt($fp, CURLOPT_ENCODING, 'none'); + $responseHeaders = curl_exec($fp); + } + $this->status_code = curl_getinfo($fp, CURLINFO_HTTP_CODE); + if (curl_errno($fp) !== CURLE_OK) { + $this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp); + $this->success = false; + } else { + // For PHPStan: `curl_exec` returns `false` only on error so the `is_string` check will always pass. + \assert(is_string($responseHeaders)); + if (curl_getinfo($fp, CURLINFO_HTTP_CONNECTCODE) !== 0) { + // TODO: Replace with `CURLOPT_SUPPRESS_CONNECT_HEADERS` once PHP 7.2 support is dropped. + $responseHeaders = \SimplePie\HTTP\Parser::prepareHeaders($responseHeaders); + } + if (\PHP_VERSION_ID < 80000) { + curl_close($fp); + } + $parser = new \SimplePie\HTTP\Parser($responseHeaders, true); + if ($parser->parse()) { + $this->set_headers($parser->headers); + $this->body = $parser->body; + if ((in_array($this->status_code, [300, 301, 302, 303, 307]) || $this->status_code > 307 && $this->status_code < 400) && ($locationHeader = $this->get_header_line('location')) !== '' && $this->redirects < $redirects) { + $this->redirects++; + $location = \SimplePie\Misc::absolutize_url($locationHeader, $url); + if ($location === false) { + $this->error = "Invalid redirect location, trying to base “{$locationHeader}” onto “{$url}”"; + $this->success = false; + return; + } + $this->permanentUrlMutable = $this->permanentUrlMutable && ($this->status_code == 301 || $this->status_code == 308); + $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen, $curl_options); + return; + } + } + } + } else { + $this->method = \SimplePie\SimplePie::FILE_SOURCE_REMOTE | \SimplePie\SimplePie::FILE_SOURCE_FSOCKOPEN; + if (($url_parts = parse_url($url)) === false) { + throw new \InvalidArgumentException('Malformed URL: ' . $url); + } + if (!isset($url_parts['host'])) { + throw new \InvalidArgumentException('Missing hostname: ' . $url); + } + $socket_host = $url_parts['host']; + if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { + $socket_host = 'ssl://' . $socket_host; + $url_parts['port'] = 443; + } + if (!isset($url_parts['port'])) { + $url_parts['port'] = 80; + } + $fp = @fsockopen($socket_host, $url_parts['port'], $errno, $errstr, $timeout); + if (!$fp) { + $this->error = 'fsockopen error: ' . $errstr; + $this->success = false; + } else { + stream_set_timeout($fp, $timeout); + if (isset($url_parts['path'])) { + if (isset($url_parts['query'])) { + $get = "$url_parts[path]?$url_parts[query]"; + } else { + $get = $url_parts['path']; + } + } else { + $get = '/'; + } + $out = "GET $get HTTP/1.1\r\n"; + $out .= "Host: $url_parts[host]\r\n"; + $out .= "User-Agent: $useragent\r\n"; + if (extension_loaded('zlib')) { + $out .= "Accept-Encoding: x-gzip,gzip,deflate\r\n"; + } + + if (isset($url_parts['user']) && isset($url_parts['pass'])) { + $out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n"; + } + foreach ($headers as $key => $value) { + $out .= "$key: $value\r\n"; + } + $out .= "Connection: Close\r\n\r\n"; + fwrite($fp, $out); + + $info = stream_get_meta_data($fp); + + $responseHeaders = ''; + while (!$info['eof'] && !$info['timed_out']) { + $responseHeaders .= fread($fp, 1160); + $info = stream_get_meta_data($fp); + } + if (!$info['timed_out']) { + $parser = new \SimplePie\HTTP\Parser($responseHeaders, true); + if ($parser->parse()) { + $this->set_headers($parser->headers); + $this->body = $parser->body; + $this->status_code = $parser->status_code; + if ((in_array($this->status_code, [300, 301, 302, 303, 307]) || $this->status_code > 307 && $this->status_code < 400) && ($locationHeader = $this->get_header_line('location')) !== '' && $this->redirects < $redirects) { + $this->redirects++; + $location = \SimplePie\Misc::absolutize_url($locationHeader, $url); + $this->permanentUrlMutable = $this->permanentUrlMutable && ($this->status_code == 301 || $this->status_code == 308); + if ($location === false) { + $this->error = "Invalid redirect location, trying to base “{$locationHeader}” onto “{$url}”"; + $this->success = false; + return; + } + $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen, $curl_options); + return; + } + if (($contentEncodingHeader = $this->get_header_line('content-encoding')) !== '') { + // Hey, we act dumb elsewhere, so let's do that here too + switch (strtolower(trim($contentEncodingHeader, "\x09\x0A\x0D\x20"))) { + case 'gzip': + case 'x-gzip': + if (($decompressed = gzdecode($this->body)) === false) { + $this->error = 'Unable to decode HTTP "gzip" stream'; + $this->success = false; + } else { + $this->body = $decompressed; + } + break; + + case 'deflate': + if (($decompressed = gzinflate($this->body)) !== false) { + $this->body = $decompressed; + } elseif (($decompressed = gzuncompress($this->body)) !== false) { + $this->body = $decompressed; + } elseif (($decompressed = gzdecode($this->body)) !== false) { + $this->body = $decompressed; + } else { + $this->error = 'Unable to decode HTTP "deflate" stream'; + $this->success = false; + } + break; + + default: + $this->error = 'Unknown content coding'; + $this->success = false; + } + } + } + } else { + $this->error = 'fsocket timed out'; + $this->success = false; + } + fclose($fp); + } + } + } else { + $this->method = \SimplePie\SimplePie::FILE_SOURCE_LOCAL | \SimplePie\SimplePie::FILE_SOURCE_FILE_GET_CONTENTS; + if (empty($url) || !is_readable($url) || false === $filebody = file_get_contents($url)) { + $this->body = ''; + $this->error = sprintf('file "%s" is not readable', $url); + $this->success = false; + } else { + $this->body = $filebody; + $this->status_code = 200; + } + } + if ($this->success) { + assert($this->body !== null); // For PHPStan + // Leading whitespace may cause XML parsing errors (XML declaration cannot be preceded by anything other than BOM) so we trim it. + // Note that unlike built-in `trim` function’s default settings, we do not trim `\x00` to avoid breaking characters in UTF-16 or UTF-32 encoded strings. + // We also only do that when the whitespace is followed by `<`, so that we do not break e.g. UTF-16LE encoded whitespace like `\n\x00` in half. + $this->body = preg_replace('/^[ \n\r\t\v]+</', '<', $this->body); + } + } + + public function get_permanent_uri(): string + { + return (string) $this->permanent_url; + } + + public function get_final_requested_uri(): string + { + return (string) $this->url; + } + + public function get_status_code(): int + { + return (int) $this->status_code; + } + + public function get_headers(): array + { + $this->maybe_update_headers(); + return $this->parsed_headers; + } + + public function has_header(string $name): bool + { + $this->maybe_update_headers(); + return $this->get_header($name) !== []; + } + + public function get_header(string $name): array + { + $this->maybe_update_headers(); + return $this->parsed_headers[strtolower($name)] ?? []; + } + + public function with_header(string $name, $value) + { + $this->maybe_update_headers(); + $new = clone $this; + + $newHeader = [ + strtolower($name) => (array) $value, + ]; + $new->set_headers($newHeader + $this->get_headers()); + + return $new; + } + + public function get_header_line(string $name): string + { + $this->maybe_update_headers(); + return implode(', ', $this->get_header($name)); + } + + public function get_body_content(): string + { + return (string) $this->body; + } + + /** + * Check if the $headers property was changed and update the internal state accordingly. + */ + private function maybe_update_headers(): void + { + if ($this->headers !== $this->last_headers) { + $this->parsed_headers = array_map( + function (string $header_line): array { + if (strpos($header_line, ',') === false) { + return [$header_line]; + } else { + return array_map('trim', explode(',', $header_line)); + } + }, + $this->headers + ); + } + $this->last_headers = $this->headers; + } + + /** + * Sets headers internally. + * + * @param array<string, non-empty-array<string>> $headers + */ + private function set_headers(array $headers): void + { + $this->parsed_headers = $headers; + $this->headers = self::flatten_headers($headers); + $this->last_headers = $this->headers; + } + + /** + * Converts PSR-7 compatible headers into a legacy format. + * + * @param array<string, non-empty-array<string>> $headers + * + * @return array<string, string> + */ + private function flatten_headers(array $headers): array + { + return array_map(function (array $values): string { + return implode(',', $values); + }, $headers); + } + + /** + * Create a File instance from another Response + * + * For BC reasons in some places there MUST be a `File` instance + * instead of a `Response` implementation + * + * @see Locator::__construct() + * @internal + */ + final public static function fromResponse(Response $response): self + { + $headers = []; + + foreach ($response->get_headers() as $name => $header) { + $headers[$name] = implode(', ', $header); + } + + /** @var File */ + $file = (new \ReflectionClass(File::class))->newInstanceWithoutConstructor(); + + $file->url = $response->get_final_requested_uri(); + $file->useragent = null; + $file->headers = $headers; + $file->body = $response->get_body_content(); + $file->status_code = $response->get_status_code(); + $file->permanent_url = $response->get_permanent_uri(); + + return $file; + } +} + +class_alias('SimplePie\File', 'SimplePie_File'); + +} + +namespace SimplePie { + +/** + * Decode 'gzip' encoded HTTP data + * + * @link http://www.gzip.org/format.txt + * @link https://www.php.net/manual/en/function.gzdecode.php + * @deprecated since SimplePie 1.9.0, use `gzdecode` function instead. + */ +class Gzdecode +{ + /** + * Compressed data + * + * @access private + * @var string + * @see gzdecode::$data + */ + public $compressed_data; + + /** + * Size of compressed data + * + * @access private + * @var int + */ + public $compressed_size; + + /** + * Minimum size of a valid gzip string + * + * @access private + * @var int + */ + public $min_compressed_size = 18; + + /** + * Current position of pointer + * + * @access private + * @var int + */ + public $position = 0; + + /** + * Flags (FLG) + * + * @access private + * @var int + */ + public $flags; + + /** + * Uncompressed data + * + * @access public + * @see gzdecode::$compressed_data + * @var string + */ + public $data; + + /** + * Modified time + * + * @access public + * @var int + */ + public $MTIME; + + /** + * Extra Flags + * + * @access public + * @var int + */ + public $XFL; + + /** + * Operating System + * + * @access public + * @var int + */ + public $OS; + + /** + * Subfield ID 1 + * + * @access public + * @see gzdecode::$extra_field + * @see gzdecode::$SI2 + * @var string + */ + public $SI1; + + /** + * Subfield ID 2 + * + * @access public + * @see gzdecode::$extra_field + * @see gzdecode::$SI1 + * @var string + */ + public $SI2; + + /** + * Extra field content + * + * @access public + * @see gzdecode::$SI1 + * @see gzdecode::$SI2 + * @var string + */ + public $extra_field; + + /** + * Original filename + * + * @access public + * @var string + */ + public $filename; + + /** + * Human readable comment + * + * @access public + * @var string + */ + public $comment; + + /** + * Don't allow anything to be set + * + * @param string $name + * @param mixed $value + */ + public function __set(string $name, $value) + { + throw new Exception("Cannot write property $name"); + } + + /** + * Set the compressed string and related properties + * + * @param string $data + */ + public function __construct(string $data) + { + $this->compressed_data = $data; + $this->compressed_size = strlen($data); + } + + /** + * Decode the GZIP stream + * + * @return bool Successfulness + */ + public function parse() + { + if ($this->compressed_size >= $this->min_compressed_size) { + $len = 0; + + // Check ID1, ID2, and CM + if (substr($this->compressed_data, 0, 3) !== "\x1F\x8B\x08") { + return false; + } + + // Get the FLG (FLaGs) + $this->flags = ord($this->compressed_data[3]); + + // FLG bits above (1 << 4) are reserved + if ($this->flags > 0x1F) { + return false; + } + + // Advance the pointer after the above + $this->position += 4; + + // MTIME + $mtime = substr($this->compressed_data, $this->position, 4); + // Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness + if (current((array) unpack('S', "\x00\x01")) === 1) { + $mtime = strrev($mtime); + } + $this->MTIME = current((array) unpack('l', $mtime)); + $this->position += 4; + + // Get the XFL (eXtra FLags) + $this->XFL = ord($this->compressed_data[$this->position++]); + + // Get the OS (Operating System) + $this->OS = ord($this->compressed_data[$this->position++]); + + // Parse the FEXTRA + if ($this->flags & 4) { + // Read subfield IDs + $this->SI1 = $this->compressed_data[$this->position++]; + $this->SI2 = $this->compressed_data[$this->position++]; + + // SI2 set to zero is reserved for future use + if ($this->SI2 === "\x00") { + return false; + } + + // Get the length of the extra field + $len = current((array) unpack('v', substr($this->compressed_data, $this->position, 2))); + $this->position += 2; + + // Check the length of the string is still valid + $this->min_compressed_size += $len + 4; + if ($this->compressed_size >= $this->min_compressed_size) { + // Set the extra field to the given data + $this->extra_field = substr($this->compressed_data, $this->position, $len); + $this->position += $len; + } else { + return false; + } + } + + // Parse the FNAME + if ($this->flags & 8) { + // Get the length of the filename + $len = strcspn($this->compressed_data, "\x00", $this->position); + + // Check the length of the string is still valid + $this->min_compressed_size += $len + 1; + if ($this->compressed_size >= $this->min_compressed_size) { + // Set the original filename to the given string + $this->filename = substr($this->compressed_data, $this->position, $len); + $this->position += $len + 1; + } else { + return false; + } + } + + // Parse the FCOMMENT + if ($this->flags & 16) { + // Get the length of the comment + $len = strcspn($this->compressed_data, "\x00", $this->position); + + // Check the length of the string is still valid + $this->min_compressed_size += $len + 1; + if ($this->compressed_size >= $this->min_compressed_size) { + // Set the original comment to the given string + $this->comment = substr($this->compressed_data, $this->position, $len); + $this->position += $len + 1; + } else { + return false; + } + } + + // Parse the FHCRC + if ($this->flags & 2) { + // Check the length of the string is still valid + $this->min_compressed_size += $len + 2; + if ($this->compressed_size >= $this->min_compressed_size) { + // Read the CRC + $crc = current((array) unpack('v', substr($this->compressed_data, $this->position, 2))); + + // Check the CRC matches + if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc) { + $this->position += 2; + } else { + return false; + } + } else { + return false; + } + } + + // Decompress the actual data + if (($data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false) { + return false; + } + + $this->data = $data; + $this->position = $this->compressed_size - 8; + + // Check CRC of data + $crc = current((array) unpack('V', substr($this->compressed_data, $this->position, 4))); + $this->position += 4; + /*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc)) + { + return false; + }*/ + + // Check ISIZE of data + $isize = current((array) unpack('V', substr($this->compressed_data, $this->position, 4))); + $this->position += 4; + if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize)) { + return false; + } + + // Wow, against all odds, we've actually got a valid gzip string + return true; + } + + return false; + } +} + +class_alias('SimplePie\Gzdecode', 'SimplePie_gzdecode'); + +} + +namespace SimplePie\HTTP { + +/** + * HTTP Client interface + * + * @internal + */ +interface Client +{ + public const METHOD_GET = 'GET'; + + /** + * send a request and return the response + * + * @param Client::METHOD_* $method + * @param array<string, string> $headers + * + * @throws ClientException if anything goes wrong requesting the data + */ + public function request(string $method, string $url, array $headers = []): Response; +} + +} + +namespace SimplePie\HTTP { + +use SimplePie\Exception as SimplePieException; + +/** + * Client exception class + * + * @internal + */ +final class ClientException extends SimplePieException +{ +} + +} + +namespace SimplePie\HTTP { + +use InvalidArgumentException; +use SimplePie\File; +use SimplePie\Misc; +use SimplePie\Registry; +use Throwable; + +/** + * HTTP Client based on \SimplePie\File + * + * @internal + */ +final class FileClient implements Client +{ + /** @var Registry */ + private $registry; + + /** @var array{timeout?: int, redirects?: int, useragent?: string, force_fsockopen?: bool, curl_options?: array<mixed>} */ + private $options; + + /** + * @param array{timeout?: int, redirects?: int, useragent?: string, force_fsockopen?: bool, curl_options?: array<mixed>} $options + */ + public function __construct(Registry $registry, array $options = []) + { + $this->registry = $registry; + $this->options = $options; + } + + /** + * send a request and return the response + * + * @param Client::METHOD_* $method + * @param array<string, string> $headers + * + * @throws ClientException if anything goes wrong requesting the data + */ + public function request(string $method, string $url, array $headers = []): Response + { + // @phpstan-ignore-next-line Enforce PHPDoc type. + if ($method !== self::METHOD_GET) { + throw new InvalidArgumentException(sprintf( + '%s(): Argument #1 ($method) only supports method "%s".', + __METHOD__, + self::METHOD_GET + ), 1); + } + + try { + $file = $this->registry->create(File::class, [ + $url, + $this->options['timeout'] ?? 10, + $this->options['redirects'] ?? 5, + $headers, + $this->options['useragent'] ?? Misc::get_default_useragent(), + $this->options['force_fsockopen'] ?? false, + $this->options['curl_options'] ?? [] + ]); + } catch (Throwable $th) { + throw new ClientException($th->getMessage(), $th->getCode(), $th); + } + + if ($file->error !== null && $file->get_status_code() === 0) { + throw new ClientException($file->error); + } + + return $file; + } +} + +} + +namespace SimplePie\HTTP { + +/** + * HTTP Response Parser + * @template Psr7Compatible of bool + */ +class Parser +{ + /** + * HTTP Version + * + * @var float + */ + public $http_version = 0.0; + + /** + * Status code + * + * @var int + */ + public $status_code = 0; + + /** + * Reason phrase + * + * @var string + */ + public $reason = ''; + + /** + * @var Psr7Compatible whether headers are compatible with PSR-7 format. + */ + private $psr7Compatible; + + /** + * Key/value pairs of the headers + * + * @phpstan-var (Psr7Compatible is true ? array<string, non-empty-array<string>> : array<string, string>) + */ + public $headers = []; + + /** + * Body of the response + * + * @var string + */ + public $body = ''; + + private const STATE_HTTP_VERSION = 'http_version'; + + private const STATE_STATUS = 'status'; + + private const STATE_REASON = 'reason'; + + private const STATE_NEW_LINE = 'new_line'; + + private const STATE_BODY = 'body'; + + private const STATE_NAME = 'name'; + + private const STATE_VALUE = 'value'; + + private const STATE_VALUE_CHAR = 'value_char'; + + private const STATE_QUOTE = 'quote'; + + private const STATE_QUOTE_ESCAPED = 'quote_escaped'; + + private const STATE_QUOTE_CHAR = 'quote_char'; + + private const STATE_CHUNKED = 'chunked'; + + private const STATE_EMIT = 'emit'; + + private const STATE_ERROR = false; + + /** + * Current state of the state machine + * + * @var self::STATE_* + */ + protected $state = self::STATE_HTTP_VERSION; + + /** + * Input data + * + * @var string + */ + protected $data = ''; + + /** + * Input data length (to avoid calling strlen() everytime this is needed) + * + * @var int + */ + protected $data_length = 0; + + /** + * Current position of the pointer + * + * @var int + */ + protected $position = 0; + + /** + * Name of the header currently being parsed + * + * @var string + */ + protected $name = ''; + + /** + * Value of the header currently being parsed + * + * @var string + */ + protected $value = ''; + + /** + * Create an instance of the class with the input data + * + * @param string $data Input data + * @param Psr7Compatible $psr7Compatible Whether the data types are in format compatible with PSR-7. + */ + public function __construct(string $data, bool $psr7Compatible = false) + { + $this->data = $data; + $this->data_length = strlen($this->data); + $this->psr7Compatible = $psr7Compatible; + } + + /** + * Parse the input data + * + * @return bool true on success, false on failure + */ + public function parse() + { + while ($this->state && $this->state !== self::STATE_EMIT && $this->has_data()) { + $state = $this->state; + $this->$state(); + } + $this->data = ''; + if ($this->state === self::STATE_EMIT || $this->state === self::STATE_BODY) { + return true; + } + + // Reset the parser state. + $this->http_version = 0.0; + $this->status_code = 0; + $this->reason = ''; + $this->headers = []; + $this->body = ''; + return false; + } + + /** + * Check whether there is data beyond the pointer + * + * @return bool true if there is further data, false if not + */ + protected function has_data() + { + return (bool) ($this->position < $this->data_length); + } + + /** + * See if the next character is LWS + * + * @return bool true if the next character is LWS, false if not + */ + protected function is_linear_whitespace() + { + return (bool) ($this->data[$this->position] === "\x09" + || $this->data[$this->position] === "\x20" + || ($this->data[$this->position] === "\x0A" + && isset($this->data[$this->position + 1]) + && ($this->data[$this->position + 1] === "\x09" || $this->data[$this->position + 1] === "\x20"))); + } + + /** + * Parse the HTTP version + * @return void + */ + protected function http_version() + { + if (strpos($this->data, "\x0A") !== false && strtoupper(substr($this->data, 0, 5)) === 'HTTP/') { + $len = strspn($this->data, '0123456789.', 5); + $http_version = substr($this->data, 5, $len); + $this->position += 5 + $len; + if (substr_count($http_version, '.') <= 1) { + $this->http_version = (float) $http_version; + $this->position += strspn($this->data, "\x09\x20", $this->position); + $this->state = self::STATE_STATUS; + } else { + $this->state = self::STATE_ERROR; + } + } else { + $this->state = self::STATE_ERROR; + } + } + + /** + * Parse the status code + * @return void + */ + protected function status() + { + if ($len = strspn($this->data, '0123456789', $this->position)) { + $this->status_code = (int) substr($this->data, $this->position, $len); + $this->position += $len; + $this->state = self::STATE_REASON; + } else { + $this->state = self::STATE_ERROR; + } + } + + /** + * Parse the reason phrase + * @return void + */ + protected function reason() + { + $len = strcspn($this->data, "\x0A", $this->position); + $this->reason = trim(substr($this->data, $this->position, $len), "\x09\x0D\x20"); + $this->position += $len + 1; + $this->state = self::STATE_NEW_LINE; + } + + private function add_header(string $name, string $value): void + { + if ($this->psr7Compatible) { + // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough. + /** @var array<string, non-empty-array<string>> */ + $headers = &$this->headers; + $headers[$name][] = $value; + } else { + // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough. + /** @var array<string, string>) */ + $headers = &$this->headers; + $headers[$name] .= ', ' . $value; + } + } + + private function replace_header(string $name, string $value): void + { + if ($this->psr7Compatible) { + // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough. + /** @var array<string, non-empty-array<string>> */ + $headers = &$this->headers; + $headers[$name] = [$value]; + } else { + // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough. + /** @var array<string, string>) */ + $headers = &$this->headers; + $headers[$name] = $value; + } + } + + /** + * Deal with a new line, shifting data around as needed + * @return void + */ + protected function new_line() + { + $this->value = trim($this->value, "\x0D\x20"); + if ($this->name !== '' && $this->value !== '') { + $this->name = strtolower($this->name); + // We should only use the last Content-Type header. c.f. issue #1 + if (isset($this->headers[$this->name]) && $this->name !== 'content-type') { + $this->add_header($this->name, $this->value); + } else { + $this->replace_header($this->name, $this->value); + } + } + $this->name = ''; + $this->value = ''; + if (substr($this->data[$this->position], 0, 2) === "\x0D\x0A") { + $this->position += 2; + $this->state = self::STATE_BODY; + } elseif ($this->data[$this->position] === "\x0A") { + $this->position++; + $this->state = self::STATE_BODY; + } else { + $this->state = self::STATE_NAME; + } + } + + /** + * Parse a header name + * @return void + */ + protected function name() + { + $len = strcspn($this->data, "\x0A:", $this->position); + if (isset($this->data[$this->position + $len])) { + if ($this->data[$this->position + $len] === "\x0A") { + $this->position += $len; + $this->state = self::STATE_NEW_LINE; + } else { + $this->name = substr($this->data, $this->position, $len); + $this->position += $len + 1; + $this->state = self::STATE_VALUE; + } + } else { + $this->state = self::STATE_ERROR; + } + } + + /** + * Parse LWS, replacing consecutive LWS characters with a single space + * @return void + */ + protected function linear_whitespace() + { + do { + if (substr($this->data, $this->position, 2) === "\x0D\x0A") { + $this->position += 2; + } elseif ($this->data[$this->position] === "\x0A") { + $this->position++; + } + $this->position += strspn($this->data, "\x09\x20", $this->position); + } while ($this->has_data() && $this->is_linear_whitespace()); + $this->value .= "\x20"; + } + + /** + * See what state to move to while within non-quoted header values + * @return void + */ + protected function value() + { + if ($this->is_linear_whitespace()) { + $this->linear_whitespace(); + } else { + switch ($this->data[$this->position]) { + case '"': + // Workaround for ETags: we have to include the quotes as + // part of the tag. + if (strtolower($this->name) === 'etag') { + $this->value .= '"'; + $this->position++; + $this->state = self::STATE_VALUE_CHAR; + break; + } + $this->position++; + $this->state = self::STATE_QUOTE; + break; + + case "\x0A": + $this->position++; + $this->state = self::STATE_NEW_LINE; + break; + + default: + $this->state = self::STATE_VALUE_CHAR; + break; + } + } + } + + /** + * Parse a header value while outside quotes + * @return void + */ + protected function value_char() + { + $len = strcspn($this->data, "\x09\x20\x0A\"", $this->position); + $this->value .= substr($this->data, $this->position, $len); + $this->position += $len; + $this->state = self::STATE_VALUE; + } + + /** + * See what state to move to while within quoted header values + * @return void + */ + protected function quote() + { + if ($this->is_linear_whitespace()) { + $this->linear_whitespace(); + } else { + switch ($this->data[$this->position]) { + case '"': + $this->position++; + $this->state = self::STATE_VALUE; + break; + + case "\x0A": + $this->position++; + $this->state = self::STATE_NEW_LINE; + break; + + case '\\': + $this->position++; + $this->state = self::STATE_QUOTE_ESCAPED; + break; + + default: + $this->state = self::STATE_QUOTE_CHAR; + break; + } + } + } + + /** + * Parse a header value while within quotes + * @return void + */ + protected function quote_char() + { + $len = strcspn($this->data, "\x09\x20\x0A\"\\", $this->position); + $this->value .= substr($this->data, $this->position, $len); + $this->position += $len; + $this->state = self::STATE_VALUE; + } + + /** + * Parse an escaped character within quotes + * @return void + */ + protected function quote_escaped() + { + $this->value .= $this->data[$this->position]; + $this->position++; + $this->state = self::STATE_QUOTE; + } + + /** + * Parse the body + * @return void + */ + protected function body() + { + $this->body = substr($this->data, $this->position); + if (!empty($this->headers['transfer-encoding'])) { + unset($this->headers['transfer-encoding']); + $this->state = self::STATE_CHUNKED; + } else { + $this->state = self::STATE_EMIT; + } + } + + /** + * Parsed a "Transfer-Encoding: chunked" body + * @return void + */ + protected function chunked() + { + if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($this->body))) { + $this->state = self::STATE_EMIT; + return; + } + + $decoded = ''; + $encoded = $this->body; + + while (true) { + $is_chunked = (bool) preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches); + if (!$is_chunked) { + // Looks like it's not chunked after all + $this->state = self::STATE_EMIT; + return; + } + + $length = hexdec(trim($matches[1])); + // For PHPStan: this will only be float when larger than PHP_INT_MAX. + // But even on 32-bit systems, it would mean 2GiB chunk, which sounds unlikely. + \assert(\is_int($length), "Length needs to be shorter than PHP_INT_MAX"); + if ($length === 0) { + // Ignore trailer headers + $this->state = self::STATE_EMIT; + $this->body = $decoded; + return; + } + + $chunk_length = strlen($matches[0]); + $decoded .= substr($encoded, $chunk_length, $length); + $encoded = substr($encoded, $chunk_length + $length + 2); + + // BC for PHP < 8.0: substr() can return bool instead of string + $encoded = ($encoded === false) ? '' : $encoded; + + if (trim($encoded) === '0' || empty($encoded)) { + $this->state = self::STATE_EMIT; + $this->body = $decoded; + return; + } + } + } + + /** + * Prepare headers (take care of proxies headers) + * + * @param string $headers Raw headers + * @param non-negative-int $count Redirection count. Default to 1. + * + * @return string + */ + public static function prepareHeaders(string $headers, int $count = 1) + { + $data = explode("\r\n\r\n", $headers, $count); + $data = array_pop($data); + if (false !== stripos($data, "HTTP/1.0 200 Connection established\r\n")) { + $exploded = explode("\r\n\r\n", $data, 2); + $data = end($exploded); + } + if (false !== stripos($data, "HTTP/1.1 200 Connection established\r\n")) { + $exploded = explode("\r\n\r\n", $data, 2); + $data = end($exploded); + } + return $data; + } +} + +class_alias('SimplePie\HTTP\Parser', 'SimplePie_HTTP_Parser'); + +} + +namespace SimplePie\HTTP { + +use Psr\Http\Message\ResponseInterface; + +/** + * HTTP Response based on a PSR-7 response + * + * This interface must be interoperable with Psr\Http\Message\ResponseInterface + * @see https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface + * + * @internal + */ +final class Psr7Response implements Response +{ + /** + * @var ResponseInterface + */ + private $response; + + /** + * @var string + */ + private $permanent_url; + + /** + * @var string + */ + private $requested_url; + + public function __construct(ResponseInterface $response, string $permanent_url, string $requested_url) + { + $this->response = $response; + $this->permanent_url = $permanent_url; + $this->requested_url = $requested_url; + } + + public function get_permanent_uri(): string + { + return $this->permanent_url; + } + + public function get_final_requested_uri(): string + { + return $this->requested_url; + } + + public function get_status_code(): int + { + return $this->response->getStatusCode(); + } + + public function get_headers(): array + { + // The filtering is probably redundant but let’s make PHPStan happy. + return array_filter($this->response->getHeaders(), function (array $header): bool { + return count($header) >= 1; + }); + } + + public function has_header(string $name): bool + { + return $this->response->hasHeader($name); + } + + public function with_header(string $name, $value) + { + return new self($this->response->withHeader($name, $value), $this->permanent_url, $this->requested_url); + } + + public function get_header(string $name): array + { + return $this->response->getHeader($name); + } + + public function get_header_line(string $name): string + { + return $this->response->getHeaderLine($name); + } + + public function get_body_content(): string + { + return $this->response->getBody()->__toString(); + } +} + +} + +namespace SimplePie\HTTP { + +use InvalidArgumentException; +use Psr\Http\Client\ClientExceptionInterface; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\UriFactoryInterface; +use Throwable; + +/** + * HTTP Client based on PSR-18 and PSR-17 implementations + * + * @internal + */ +final class Psr18Client implements Client +{ + /** + * @var ClientInterface + */ + private $httpClient; + + /** + * @var RequestFactoryInterface + */ + private $requestFactory; + + /** + * @var UriFactoryInterface + */ + private $uriFactory; + + /** + * @var int + */ + private $allowedRedirects = 5; + + public function __construct(ClientInterface $httpClient, RequestFactoryInterface $requestFactory, UriFactoryInterface $uriFactory) + { + $this->httpClient = $httpClient; + $this->requestFactory = $requestFactory; + $this->uriFactory = $uriFactory; + } + + public function getHttpClient(): ClientInterface + { + return $this->httpClient; + } + + public function getRequestFactory(): RequestFactoryInterface + { + return $this->requestFactory; + } + + public function getUriFactory(): UriFactoryInterface + { + return $this->uriFactory; + } + + /** + * send a request and return the response + * + * @param Client::METHOD_* $method + * @param string $url + * @param array<string,string|string[]> $headers + * + * @throws ClientException if anything goes wrong requesting the data + */ + public function request(string $method, string $url, array $headers = []): Response + { + if ($method !== self::METHOD_GET) { + throw new InvalidArgumentException(sprintf( + '%s(): Argument #1 ($method) only supports method "%s".', + __METHOD__, + self::METHOD_GET + ), 1); + } + + if (preg_match('/^http(s)?:\/\//i', $url)) { + return $this->requestUrl($method, $url, $headers); + } + + return $this->requestLocalFile($url); + } + + /** + * @param array<string,string|string[]> $headers + */ + private function requestUrl(string $method, string $url, array $headers): Response + { + $permanentUrl = $url; + $requestedUrl = $url; + $remainingRedirects = $this->allowedRedirects; + + $request = $this->requestFactory->createRequest( + $method, + $this->uriFactory->createUri($requestedUrl) + ); + + foreach ($headers as $name => $value) { + $request = $request->withHeader($name, $value); + } + + do { + $followRedirect = false; + + try { + $response = $this->httpClient->sendRequest($request); + } catch (ClientExceptionInterface $th) { + throw new ClientException($th->getMessage(), $th->getCode(), $th); + } + + $statusCode = $response->getStatusCode(); + + // If we have a redirect + if (in_array($statusCode, [300, 301, 302, 303, 307]) && $response->hasHeader('Location')) { + // Prevent infinity redirect loops + if ($remainingRedirects <= 0) { + break; + } + + $remainingRedirects--; + $followRedirect = true; + + $requestedUrl = $response->getHeaderLine('Location'); + + if ($statusCode === 301) { + $permanentUrl = $requestedUrl; + } + + $request = $request->withUri($this->uriFactory->createUri($requestedUrl)); + } + } while ($followRedirect); + + return new Psr7Response($response, $permanentUrl, $requestedUrl); + } + + private function requestLocalFile(string $path): Response + { + if (!is_readable($path)) { + throw new ClientException(sprintf('file "%s" is not readable', $path)); + } + + try { + $raw = file_get_contents($path); + } catch (Throwable $th) { + throw new ClientException($th->getMessage(), $th->getCode(), $th); + } + + if ($raw === false) { + throw new ClientException('file_get_contents() could not read the file', 1); + } + + return new RawTextResponse($raw, $path); + } +} + +} + +namespace SimplePie\HTTP { + +/** + * HTTP Response for rax text + * + * This interface must be interoperable with Psr\Http\Message\ResponseInterface + * @see https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface + * + * @internal + */ +final class RawTextResponse implements Response +{ + /** + * @var string + */ + private $raw_text; + + /** + * @var string + */ + private $permanent_url; + + /** + * @var array<non-empty-array<string>> + */ + private $headers = []; + + /** + * @var string + */ + private $requested_url; + + public function __construct(string $raw_text, string $filepath) + { + $this->raw_text = $raw_text; + $this->permanent_url = $filepath; + $this->requested_url = $filepath; + } + + public function get_permanent_uri(): string + { + return $this->permanent_url; + } + + public function get_final_requested_uri(): string + { + return $this->requested_url; + } + + public function get_status_code(): int + { + return 200; + } + + public function get_headers(): array + { + return $this->headers; + } + + public function has_header(string $name): bool + { + return isset($this->headers[strtolower($name)]); + } + + public function get_header(string $name): array + { + return isset($this->headers[strtolower($name)]) ? $this->headers[$name] : []; + } + + public function with_header(string $name, $value) + { + $new = clone $this; + + $newHeader = [ + strtolower($name) => (array) $value, + ]; + $new->headers = $newHeader + $this->headers; + + return $new; + } + + public function get_header_line(string $name): string + { + return isset($this->headers[strtolower($name)]) ? implode(", ", $this->headers[$name]) : ''; + } + + public function get_body_content(): string + { + return $this->raw_text; + } +} + +} + +namespace SimplePie\HTTP { + +/** + * HTTP Response interface + * + * This interface must be interoperable with Psr\Http\Message\ResponseInterface + * @see https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface + * + * @internal + */ +interface Response +{ + /** + * Return the string representation of the permanent URI of the requested resource + * (the first location after a prefix of (only) permanent redirects). + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + */ + public function get_permanent_uri(): string; + + /** + * Return the string representation of the final requested URL after following all redirects. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + */ + public function get_final_requested_uri(): string; + + /** + * Gets the response status code. + * + * The status code is a 3-digit integer result code of the server's attempt + * to understand and satisfy the request. + * + * @return int Status code. + */ + public function get_status_code(): int; + + /** + * Retrieves all message header values. + * + * The keys represent the header name as it will be sent over the wire, and + * each value is an array of strings associated with the header. + * + * // Represent the headers as a string + * foreach ($message->get_headers() as $name => $values) { + * echo $name . ': ' . implode(', ', $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->get_headers() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * @return array<non-empty-array<string>> Returns an associative array of the message's headers. + * Each key MUST be a header name, and each value MUST be an array of + * strings for that header. + */ + public function get_headers(): array; + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function has_header(string $name): bool; + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function get_header(string $name): array; + + /** + * Return an instance with the provided value replacing the specified header. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|non-empty-array<string> $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function with_header(string $name, $value); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function get_header_line(string $name): string; + + /** + * get the body as string + * + * @return string + */ + public function get_body_content(): string; +} + +} + +namespace SimplePie { + +/** + * IRI parser/serialiser/normaliser + * + * @property ?string $scheme + * @property ?string $userinfo + * @property ?string $host + * @property ?int $port + * @property-write int|string|null $port + * @property ?string $authority + * @property string $path + * @property ?string $query + * @property ?string $fragment + */ +class IRI +{ + /** + * Scheme + * + * @var ?string + */ + protected $scheme = null; + + /** + * User Information + * + * @var ?string + */ + protected $iuserinfo = null; + + /** + * ihost + * + * @var ?string + */ + protected $ihost = null; + + /** + * Port + * + * @var ?int + */ + protected $port = null; + + /** + * ipath + * + * @var string + */ + protected $ipath = ''; + + /** + * iquery + * + * @var ?string + */ + protected $iquery = null; + + /** + * ifragment + * + * @var ?string + */ + protected $ifragment = null; + + /** + * Normalization database + * + * Each key is the scheme, each value is an array with each key as the IRI + * part and value as the default value for that part. + * + * @var array<string, array<string, mixed>> + */ + protected $normalization = [ + 'acap' => [ + 'port' => 674 + ], + 'dict' => [ + 'port' => 2628 + ], + 'file' => [ + 'ihost' => 'localhost' + ], + 'http' => [ + 'port' => 80, + 'ipath' => '/' + ], + 'https' => [ + 'port' => 443, + 'ipath' => '/' + ], + ]; + + /** + * Return the entire IRI when you try and read the object as a string + * + * @return string + */ + public function __toString() + { + return (string) $this->get_iri(); + } + + /** + * Overload __set() to provide access via properties + * + * @param string $name Property name + * @param mixed $value Property value + * @return void + */ + public function __set(string $name, $value) + { + $callable = [$this, 'set_' . $name]; + if (is_callable($callable)) { + call_user_func($callable, $value); + } elseif ( + $name === 'iauthority' + || $name === 'iuserinfo' + || $name === 'ihost' + || $name === 'ipath' + || $name === 'iquery' + || $name === 'ifragment' + ) { + call_user_func([$this, 'set_' . substr($name, 1)], $value); + } + } + + /** + * Overload __get() to provide access via properties + * + * @param string $name Property name + * @return mixed + */ + public function __get(string $name) + { + // isset() returns false for null, we don't want to do that + // Also why we use array_key_exists below instead of isset() + $props = get_object_vars($this); + + if ( + $name === 'iri' || + $name === 'uri' || + $name === 'iauthority' || + $name === 'authority' + ) { + $return = $this->{"get_$name"}(); + } elseif (array_key_exists($name, $props)) { + $return = $this->$name; + } + // host -> ihost + elseif (array_key_exists($prop = 'i' . $name, $props)) { + $name = $prop; + $return = $this->$prop; + } + // ischeme -> scheme + elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) { + $name = $prop; + $return = $this->$prop; + } else { + trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); + $return = null; + } + + if ($return === null && isset($this->normalization[$this->scheme][$name])) { + return $this->normalization[$this->scheme][$name]; + } + + return $return; + } + + /** + * Overload __isset() to provide access via properties + * + * @param string $name Property name + * @return bool + */ + public function __isset(string $name) + { + return method_exists($this, 'get_' . $name) || isset($this->$name); + } + + /** + * Overload __unset() to provide access via properties + * + * @param string $name Property name + * @return void + */ + public function __unset(string $name) + { + $callable = [$this, 'set_' . $name]; + if (is_callable($callable)) { + call_user_func($callable, ''); + } + } + + /** + * Create a new IRI object, from a specified string + * + * @param string|null $iri + */ + public function __construct(?string $iri = null) + { + $this->set_iri($iri); + } + + /** + * Clean up + * @return void + */ + public function __destruct() + { + $this->set_iri(null, true); + $this->set_path(null, true); + $this->set_authority(null, true); + } + + /** + * Create a new IRI object by resolving a relative IRI + * + * Returns false if $base is not absolute, otherwise an IRI. + * + * @param IRI|string $base (Absolute) Base IRI + * @param IRI|string $relative Relative IRI + * @return IRI|false + */ + public static function absolutize($base, $relative) + { + if (!($relative instanceof IRI)) { + $relative = new IRI($relative); + } + if (!$relative->is_valid()) { + return false; + } elseif ($relative->scheme !== null) { + return clone $relative; + } else { + if (!($base instanceof IRI)) { + $base = new IRI($base); + } + if ($base->scheme !== null && $base->is_valid()) { + if ($relative->get_iri() !== '') { + if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) { + $target = clone $relative; + $target->scheme = $base->scheme; + } else { + $target = new IRI(); + $target->scheme = $base->scheme; + $target->iuserinfo = $base->iuserinfo; + $target->ihost = $base->ihost; + $target->port = $base->port; + if ($relative->ipath !== '') { + if ($relative->ipath[0] === '/') { + $target->ipath = $relative->ipath; + } elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') { + $target->ipath = '/' . $relative->ipath; + } elseif (($last_segment = strrpos($base->ipath, '/')) !== false) { + $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; + } else { + $target->ipath = $relative->ipath; + } + $target->ipath = $target->remove_dot_segments($target->ipath); + $target->iquery = $relative->iquery; + } else { + $target->ipath = $base->ipath; + if ($relative->iquery !== null) { + $target->iquery = $relative->iquery; + } elseif ($base->iquery !== null) { + $target->iquery = $base->iquery; + } + } + $target->ifragment = $relative->ifragment; + } + } else { + $target = clone $base; + $target->ifragment = null; + } + $target->scheme_normalization(); + return $target; + } + + return false; + } + } + + /** + * Parse an IRI into scheme/authority/path/query/fragment segments + * + * @param string $iri + * @return array{ + * scheme: string|null, + * authority: string|null, + * path: string, + * query: string|null, + * fragment: string|null, + * }|false + */ + protected function parse_iri(string $iri) + { + $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); + if (preg_match('/^(?:(?P<scheme>[^:\/?#]+):)?(:?\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(?:\?(?P<query>[^#]*))?(?:#(?P<fragment>.*))?$/', $iri, $match, \PREG_UNMATCHED_AS_NULL)) { + // TODO: Remove once we require PHP ≥ 7.4. + $match['query'] = $match['query'] ?? null; + $match['fragment'] = $match['fragment'] ?? null; + return $match; + } + + // This can occur when a paragraph is accidentally parsed as a URI + return false; + } + + /** + * Remove dot segments from a path + * + * @param string $input + * @return string + */ + protected function remove_dot_segments(string $input) + { + $output = ''; + while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') { + // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, + if (strpos($input, '../') === 0) { + $input = substr($input, 3); + } elseif (strpos($input, './') === 0) { + $input = substr($input, 2); + } + // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, + elseif (strpos($input, '/./') === 0) { + $input = substr($input, 2); + } elseif ($input === '/.') { + $input = '/'; + } + // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, + elseif (strpos($input, '/../') === 0) { + $input = substr($input, 3); + $output = substr_replace($output, '', intval(strrpos($output, '/'))); + } elseif ($input === '/..') { + $input = '/'; + $output = substr_replace($output, '', intval(strrpos($output, '/'))); + } + // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, + elseif ($input === '.' || $input === '..') { + $input = ''; + } + // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer + elseif (($pos = strpos($input, '/', 1)) !== false) { + $output .= substr($input, 0, $pos); + $input = substr_replace($input, '', 0, $pos); + } else { + $output .= $input; + $input = ''; + } + } + return $output . $input; + } + + /** + * Replace invalid character with percent encoding + * + * @param string $string Input string + * @param string $extra_chars Valid characters not in iunreserved or + * iprivate (this is ASCII-only) + * @param bool $iprivate Allow iprivate + * @return string + */ + protected function replace_invalid_with_pct_encoding(string $string, string $extra_chars, bool $iprivate = false) + { + // Normalize as many pct-encoded sections as possible + $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', [$this, 'remove_iunreserved_percent_encoded'], $string); + \assert(\is_string($string), "For PHPStan: Should not occur, the regex is valid"); + + // Replace invalid percent characters + $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string); + \assert(\is_string($string), "For PHPStan: Should not occur, the regex is valid"); + + // Add unreserved and % to $extra_chars (the latter is safe because all + // pct-encoded sections are now valid). + $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; + + // Now replace any bytes that aren't allowed with their pct-encoded versions + $position = 0; + $strlen = strlen($string); + while (($position += strspn($string, $extra_chars, $position)) < $strlen) { + $value = ord($string[$position]); + $character = 0; + + // Start position + $start = $position; + + // By default we are valid + $valid = true; + + // No one byte sequences are valid due to the while. + // Two byte sequence: + if (($value & 0xE0) === 0xC0) { + $character = ($value & 0x1F) << 6; + $length = 2; + $remaining = 1; + } + // Three byte sequence: + elseif (($value & 0xF0) === 0xE0) { + $character = ($value & 0x0F) << 12; + $length = 3; + $remaining = 2; + } + // Four byte sequence: + elseif (($value & 0xF8) === 0xF0) { + $character = ($value & 0x07) << 18; + $length = 4; + $remaining = 3; + } + // Invalid byte: + else { + $valid = false; + $length = 1; + $remaining = 0; + } + + if ($remaining) { + if ($position + $length <= $strlen) { + for ($position++; $remaining; $position++) { + $value = ord($string[$position]); + + // Check that the byte is valid, then add it to the character: + if (($value & 0xC0) === 0x80) { + $character |= ($value & 0x3F) << (--$remaining * 6); + } + // If it is invalid, count the sequence as invalid and reprocess the current byte: + else { + $valid = false; + $position--; + break; + } + } + } else { + $position = $strlen - 1; + $valid = false; + } + } + + // Percent encode anything invalid or not in ucschar + if ( + // Invalid sequences + !$valid + // Non-shortest form sequences are invalid + || $length > 1 && $character <= 0x7F + || $length > 2 && $character <= 0x7FF + || $length > 3 && $character <= 0xFFFF + // Outside of range of ucschar codepoints + // Noncharacters + || ($character & 0xFFFE) === 0xFFFE + || $character >= 0xFDD0 && $character <= 0xFDEF + || ( + // Everything else not in ucschar + $character > 0xD7FF && $character < 0xF900 + || $character < 0xA0 + || $character > 0xEFFFD + ) + && ( + // Everything not in iprivate, if it applies + !$iprivate + || $character < 0xE000 + || $character > 0x10FFFD + ) + ) { + // If we were a character, pretend we weren't, but rather an error. + if ($valid) { + $position--; + } + + for ($j = $start; $j <= $position; $j++) { + $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1); + $j += 2; + $position += 2; + $strlen += 2; + } + } + } + + return $string; + } + + /** + * Callback function for preg_replace_callback. + * + * Removes sequences of percent encoded bytes that represent UTF-8 + * encoded characters in iunreserved + * + * @param array{string} $match PCRE match, a capture group #0 consisting of a sequence of valid percent-encoded bytes + * @return string Replacement + */ + protected function remove_iunreserved_percent_encoded(array $match) + { + // As we just have valid percent encoded sequences we can just explode + // and ignore the first member of the returned array (an empty string). + $bytes = explode('%', $match[0]); + + // Initialize the new string (this is what will be returned) and that + // there are no bytes remaining in the current sequence (unsurprising + // at the first byte!). + $string = ''; + $remaining = 0; + + // these variables will be initialized in the loop but PHPStan is not able to detect it currently + $start = 0; + $character = 0; + $length = 0; + $valid = true; + + // Loop over each and every byte, and set $value to its value + for ($i = 1, $len = count($bytes); $i < $len; $i++) { + $value = hexdec($bytes[$i]); + + // If we're the first byte of sequence: + if (!$remaining) { + // Start position + $start = $i; + + // By default we are valid + $valid = true; + + // One byte sequence: + if ($value <= 0x7F) { + $character = $value; + $length = 1; + } + // Two byte sequence: + elseif (($value & 0xE0) === 0xC0) { + $character = ($value & 0x1F) << 6; + $length = 2; + $remaining = 1; + } + // Three byte sequence: + elseif (($value & 0xF0) === 0xE0) { + $character = ($value & 0x0F) << 12; + $length = 3; + $remaining = 2; + } + // Four byte sequence: + elseif (($value & 0xF8) === 0xF0) { + $character = ($value & 0x07) << 18; + $length = 4; + $remaining = 3; + } + // Invalid byte: + else { + $valid = false; + $remaining = 0; + } + } + // Continuation byte: + else { + // Check that the byte is valid, then add it to the character: + if (($value & 0xC0) === 0x80) { + $remaining--; + $character |= ($value & 0x3F) << ($remaining * 6); + } + // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: + else { + $valid = false; + $remaining = 0; + $i--; + } + } + + // If we've reached the end of the current byte sequence, append it to Unicode::$data + if (!$remaining) { + // Percent encode anything invalid or not in iunreserved + if ( + // Invalid sequences + !$valid + // Non-shortest form sequences are invalid + || $length > 1 && $character <= 0x7F + || $length > 2 && $character <= 0x7FF + || $length > 3 && $character <= 0xFFFF + // Outside of range of iunreserved codepoints + || $character < 0x2D + || $character > 0xEFFFD + // Noncharacters + || ($character & 0xFFFE) === 0xFFFE + || $character >= 0xFDD0 && $character <= 0xFDEF + // Everything else not in iunreserved (this is all BMP) + || $character === 0x2F + || $character > 0x39 && $character < 0x41 + || $character > 0x5A && $character < 0x61 + || $character > 0x7A && $character < 0x7E + || $character > 0x7E && $character < 0xA0 + || $character > 0xD7FF && $character < 0xF900 + ) { + for ($j = $start; $j <= $i; $j++) { + $string .= '%' . strtoupper($bytes[$j]); + } + } else { + for ($j = $start; $j <= $i; $j++) { + // Cast for PHPStan, this will always be a number between 0 and 0xFF so hexdec will return int. + $string .= chr((int) hexdec($bytes[$j])); + } + } + } + } + + // If we have any bytes left over they are invalid (i.e., we are + // mid-way through a multi-byte sequence) + if ($remaining) { + for ($j = $start; $j < $len; $j++) { + $string .= '%' . strtoupper($bytes[$j]); + } + } + + return $string; + } + + /** + * @return void + */ + protected function scheme_normalization() + { + if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) { + $this->iuserinfo = null; + } + if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) { + $this->ihost = null; + } + if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) { + $this->port = null; + } + if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) { + $this->ipath = ''; + } + if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) { + $this->iquery = null; + } + if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) { + $this->ifragment = null; + } + } + + /** + * Check if the object represents a valid IRI. This needs to be done on each + * call as some things change depending on another part of the IRI. + * + * @return bool + */ + public function is_valid() + { + if ($this->ipath === '') { + return true; + } + + $isauthority = $this->iuserinfo !== null || $this->ihost !== null || + $this->port !== null; + if ($isauthority && $this->ipath[0] === '/') { + return true; + } + + if (!$isauthority && (substr($this->ipath, 0, 2) === '//')) { + return false; + } + + // Relative urls cannot have a colon in the first path segment (and the + // slashes themselves are not included so skip the first character). + if (!$this->scheme && !$isauthority && + strpos($this->ipath, ':') !== false && + strpos($this->ipath, '/', 1) !== false && + strpos($this->ipath, ':') < strpos($this->ipath, '/', 1)) { + return false; + } + + return true; + } + + /** + * Set the entire IRI. Returns true on success, false on failure (if there + * are any invalid characters). + * + * @param string|null $iri + * @return bool + */ + public function set_iri(?string $iri, bool $clear_cache = false) + { + static $cache; + if ($clear_cache) { + $cache = null; + return false; + } + if (!$cache) { + $cache = []; + } + + if ($iri === null) { + return true; + } elseif (isset($cache[$iri])) { + [ + $this->scheme, + $this->iuserinfo, + $this->ihost, + $this->port, + $this->ipath, + $this->iquery, + $this->ifragment, + $return + ] = $cache[$iri]; + + return $return; + } + + $parsed = $this->parse_iri((string) $iri); + if (!$parsed) { + return false; + } + + $return = $this->set_scheme($parsed['scheme']) + && $this->set_authority($parsed['authority']) + && $this->set_path($parsed['path']) + && $this->set_query($parsed['query']) + && $this->set_fragment($parsed['fragment']); + + $cache[$iri] = [ + $this->scheme, + $this->iuserinfo, + $this->ihost, + $this->port, + $this->ipath, + $this->iquery, + $this->ifragment, + $return + ]; + + return $return; + } + + /** + * Set the scheme. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @param string|null $scheme + * @return bool + */ + public function set_scheme(?string $scheme) + { + if ($scheme === null) { + $this->scheme = null; + } elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) { + $this->scheme = null; + return false; + } else { + $this->scheme = strtolower($scheme); + } + return true; + } + + /** + * Set the authority. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @param string|null $authority + * @return bool + */ + public function set_authority(?string $authority, bool $clear_cache = false) + { + static $cache; + if ($clear_cache) { + $cache = null; + return false; + } + if (!$cache) { + $cache = []; + } + + if ($authority === null) { + $this->iuserinfo = null; + $this->ihost = null; + $this->port = null; + return true; + } elseif (isset($cache[$authority])) { + [ + $this->iuserinfo, + $this->ihost, + $this->port, + $return + ] = $cache[$authority]; + + return $return; + } + + $remaining = $authority; + if (($iuserinfo_end = strrpos($remaining, '@')) !== false) { + // Cast for PHPStan on PHP < 8.0. It does not detect that + // the range is not flipped so substr cannot return false. + $iuserinfo = (string) substr($remaining, 0, $iuserinfo_end); + $remaining = substr($remaining, $iuserinfo_end + 1); + } else { + $iuserinfo = null; + } + if (($port_start = strpos($remaining, ':', intval(strpos($remaining, ']')))) !== false) { + $port = substr($remaining, $port_start + 1); + if ($port === false) { + $port = null; + } + $remaining = substr($remaining, 0, $port_start); + } else { + $port = null; + } + + $return = $this->set_userinfo($iuserinfo) && + $this->set_host($remaining) && + $this->set_port($port); + + $cache[$authority] = [ + $this->iuserinfo, + $this->ihost, + $this->port, + $return + ]; + + return $return; + } + + /** + * Set the iuserinfo. + * + * @param string|null $iuserinfo + * @return bool + */ + public function set_userinfo(?string $iuserinfo) + { + if ($iuserinfo === null) { + $this->iuserinfo = null; + } else { + $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); + $this->scheme_normalization(); + } + + return true; + } + + /** + * Set the ihost. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @param string|null $ihost + * @return bool + */ + public function set_host(?string $ihost) + { + if ($ihost === null) { + $this->ihost = null; + return true; + } elseif (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { + if (\SimplePie\Net\IPv6::check_ipv6(substr($ihost, 1, -1))) { + $this->ihost = '[' . \SimplePie\Net\IPv6::compress(substr($ihost, 1, -1)) . ']'; + } else { + $this->ihost = null; + return false; + } + } else { + $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); + + // Lowercase, but ignore pct-encoded sections (as they should + // remain uppercase). This must be done after the previous step + // as that can add unescaped characters. + $position = 0; + $strlen = strlen($ihost); + while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) { + if ($ihost[$position] === '%') { + $position += 3; + } else { + $ihost[$position] = strtolower($ihost[$position]); + $position++; + } + } + + $this->ihost = $ihost; + } + + $this->scheme_normalization(); + + return true; + } + + /** + * Set the port. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @param string|int|null $port + * @return bool + */ + public function set_port($port) + { + if ($port === null) { + $this->port = null; + return true; + } elseif (strspn((string) $port, '0123456789') === strlen((string) $port)) { + $this->port = (int) $port; + $this->scheme_normalization(); + return true; + } + + $this->port = null; + return false; + } + + /** + * Set the ipath. + * + * @param string|null $ipath + * @return bool + */ + public function set_path(?string $ipath, bool $clear_cache = false) + { + static $cache; + if ($clear_cache) { + $cache = null; + return false; + } + if (!$cache) { + $cache = []; + } + + $ipath = (string) $ipath; + + if (isset($cache[$ipath])) { + $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; + } else { + $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); + $removed = $this->remove_dot_segments($valid); + + $cache[$ipath] = [$valid, $removed]; + $this->ipath = ($this->scheme !== null) ? $removed : $valid; + } + + $this->scheme_normalization(); + return true; + } + + /** + * Set the iquery. + * + * @param string|null $iquery + * @return bool + */ + public function set_query(?string $iquery) + { + if ($iquery === null) { + $this->iquery = null; + } else { + $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); + $this->scheme_normalization(); + } + return true; + } + + /** + * Set the ifragment. + * + * @param string|null $ifragment + * @return bool + */ + public function set_fragment(?string $ifragment) + { + if ($ifragment === null) { + $this->ifragment = null; + } else { + $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); + $this->scheme_normalization(); + } + return true; + } + + /** + * Convert an IRI to a URI (or parts thereof) + * + * @param string $string + * @return string + */ + public function to_uri(string $string) + { + static $non_ascii; + if (!$non_ascii) { + $non_ascii = implode('', range("\x80", "\xFF")); + } + + $position = 0; + $strlen = strlen($string); + while (($position += strcspn($string, $non_ascii, $position)) < $strlen) { + $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1); + $position += 3; + $strlen += 2; + } + + return $string; + } + + /** + * Get the complete IRI + * + * @return string|false + */ + public function get_iri() + { + if (!$this->is_valid()) { + return false; + } + + $iri = ''; + if ($this->scheme !== null) { + $iri .= $this->scheme . ':'; + } + if (($iauthority = $this->get_iauthority()) !== null) { + $iri .= '//' . $iauthority; + } + if ($this->ipath !== '') { + $iri .= $this->ipath; + } elseif (!empty($this->normalization[$this->scheme]['ipath']) && $iauthority !== null && $iauthority !== '') { + $iri .= $this->normalization[$this->scheme]['ipath']; + } + if ($this->iquery !== null) { + $iri .= '?' . $this->iquery; + } + if ($this->ifragment !== null) { + $iri .= '#' . $this->ifragment; + } + + return $iri; + } + + /** + * Get the complete URI + * + * @return string + */ + public function get_uri() + { + return $this->to_uri((string) $this->get_iri()); + } + + /** + * Get the complete iauthority + * + * @return ?string + */ + protected function get_iauthority() + { + if ($this->iuserinfo !== null || $this->ihost !== null || $this->port !== null) { + $iauthority = ''; + if ($this->iuserinfo !== null) { + $iauthority .= $this->iuserinfo . '@'; + } + if ($this->ihost !== null) { + $iauthority .= $this->ihost; + } + if ($this->port !== null && $this->port !== 0) { + $iauthority .= ':' . $this->port; + } + return $iauthority; + } + + return null; + } + + /** + * Get the complete authority + * + * @return ?string + */ + protected function get_authority() + { + $iauthority = $this->get_iauthority(); + if (is_string($iauthority)) { + return $this->to_uri($iauthority); + } + + return $iauthority; + } +} + +class_alias('SimplePie\IRI', 'SimplePie_IRI'); + +} + +namespace SimplePie { + +/** + * Manages all item-related data + * + * Used by {@see \SimplePie\SimplePie::get_item()} and {@see \SimplePie\SimplePie::get_items()} + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_item_class()} + */ +class Item implements RegistryAware +{ + /** + * Parent feed + * + * @access private + * @var \SimplePie\SimplePie + */ + public $feed; + + /** + * Raw data + * + * @access private + * @var array<string, mixed> + */ + public $data = []; + + /** + * Registry object + * + * @see set_registry + * @var \SimplePie\Registry + */ + protected $registry; + + /** + * @var Sanitize|null + */ + private $sanitize = null; + + /** + * Create a new item object + * + * This is usually used by {@see \SimplePie\SimplePie::get_items} and + * {@see \SimplePie\SimplePie::get_item}. Avoid creating this manually. + * + * @param \SimplePie\SimplePie $feed Parent feed + * @param array<string, mixed> $data Raw data + */ + public function __construct(\SimplePie\SimplePie $feed, array $data) + { + $this->feed = $feed; + $this->data = $data; + } + + /** + * Set the registry handler + * + * This is usually used by {@see \SimplePie\Registry::create} + * + * @since 1.3 + * @param \SimplePie\Registry $registry + * @return void + */ + public function set_registry(\SimplePie\Registry $registry) + { + $this->registry = $registry; + } + + /** + * Get a string representation of the item + * + * @return string + */ + public function __toString() + { + return md5(serialize($this->data)); + } + + /** + * Remove items that link back to this before destroying this object + */ + public function __destruct() + { + if (!gc_enabled()) { + unset($this->feed); + } + } + + /** + * Get data for an item-level element + * + * This method allows you to get access to ANY element/attribute that is a + * sub-element of the item/entry tag. + * + * See {@see \SimplePie\SimplePie::get_feed_tags()} for a description of the return value + * + * @since 1.0 + * @see http://simplepie.org/wiki/faq/supported_xml_namespaces + * @param string $namespace The URL of the XML namespace of the elements you're trying to access + * @param string $tag Tag name + * @return array<array<string, mixed>>|null + */ + public function get_item_tags(string $namespace, string $tag) + { + if (isset($this->data['child'][$namespace][$tag])) { + return $this->data['child'][$namespace][$tag]; + } + + return null; + } + + /** + * Get base URL of the item itself. + * Returns `<xml:base>` or feed base URL. + * Similar to `Item::get_base()` but can safely be used during initialisation methods + * such as `Item::get_links()` (`Item::get_base()` and `Item::get_links()` call each-other) + * and is not affected by enclosures. + * + * @param array<string, mixed> $element + * @see get_base + */ + private function get_own_base(array $element = []): string + { + if (!empty($element['xml_base_explicit']) && isset($element['xml_base'])) { + return $element['xml_base']; + } + return $this->feed->get_base(); + } + + /** + * Get the base URL value. + * Uses `<xml:base>`, or item link, or enclosure link, or feed base URL. + * + * @param array<string, mixed> $element + * @return string + */ + public function get_base(array $element = []) + { + if (!empty($element['xml_base_explicit']) && isset($element['xml_base'])) { + return $element['xml_base']; + } + $link = $this->get_permalink(); + if ($link != null) { + return $link; + } + return $this->feed->get_base($element); + } + + /** + * Sanitize feed data + * + * @access private + * @see \SimplePie\SimplePie::sanitize() + * @param string $data Data to sanitize + * @param int-mask-of<SimplePie::CONSTRUCT_*> $type + * @param string $base Base URL to resolve URLs against + * @return string Sanitized data + */ + public function sanitize(string $data, int $type, string $base = '') + { + // This really returns string|false but changing encoding is uncommon and we are going to deprecate it, so let’s just lie to PHPStan in the interest of cleaner annotations. + return $this->feed->sanitize($data, $type, $base); + } + + /** + * Get the parent feed + * + * Note: this may not work as you think for multifeeds! + * + * @link http://simplepie.org/faq/typical_multifeed_gotchas#missing_data_from_feed + * @since 1.0 + * @return \SimplePie\SimplePie + */ + public function get_feed() + { + return $this->feed; + } + + /** + * Get the unique identifier for the item + * + * This is usually used when writing code to check for new items in a feed. + * + * Uses `<atom:id>`, `<guid>`, `<dc:identifier>` or the `about` attribute + * for RDF. If none of these are supplied (or `$hash` is true), creates an + * MD5 hash based on the permalink, title and content. + * + * @since Beta 2 + * @param bool $hash Should we force using a hash instead of the supplied ID? + * @param string|false $fn User-supplied function to generate an hash + * @return string|null + */ + public function get_id(bool $hash = false, $fn = 'md5') + { + if (!$hash) { + if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'id')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'id')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'guid')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'identifier')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'identifier')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif (isset($this->data['attribs'][\SimplePie\SimplePie::NAMESPACE_RDF]['about'])) { + return $this->sanitize($this->data['attribs'][\SimplePie\SimplePie::NAMESPACE_RDF]['about'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + } + if ($fn === false) { + return null; + } elseif (!is_callable($fn)) { + trigger_error('User-supplied function $fn must be callable', E_USER_WARNING); + $fn = 'md5'; + } + return call_user_func( + $fn, + $this->get_permalink().$this->get_title().$this->get_content() + ); + } + + /** + * Get the title of the item + * + * Uses `<atom:title>`, `<title>` or `<dc:title>` + * + * @since Beta 2 (previously called `get_item_title` since 0.8) + * @return string|null + */ + public function get_title() + { + if (!isset($this->data['title'])) { + if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'title')) { + $this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'title')) { + $this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'title')) { + $this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'title')) { + $this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'title')) { + $this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'title')) { + $this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'title')) { + $this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $this->data['title'] = null; + } + } + return $this->data['title']; + } + + /** + * Get the content for the item + * + * Prefers summaries over full content , but will return full content if a + * summary does not exist. + * + * To prefer full content instead, use {@see get_content} + * + * Uses `<atom:summary>`, `<description>`, `<dc:description>` or + * `<itunes:subtitle>` + * + * @since 0.8 + * @param bool $description_only Should we avoid falling back to the content? + * @return string|null + */ + public function get_description(bool $description_only = false) + { + if (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'summary')) && + ($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) { + return $return; + } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'summary')) && + ($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) { + return $return; + } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'description')) && + ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($tags[0])))) { + return $return; + } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'description')) && + ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($tags[0])))) { + return $return; + } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'description')) && + ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT))) { + return $return; + } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'description')) && + ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT))) { + return $return; + } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'summary')) && + ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($tags[0])))) { + return $return; + } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'subtitle')) && + ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT))) { + return $return; + } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'description')) && + ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML))) { + return $return; + } elseif (!$description_only) { + return $this->get_content(true); + } + + return null; + } + + /** + * Get the content for the item + * + * Prefers full content over summaries, but will return a summary if full + * content does not exist. + * + * To prefer summaries instead, use {@see get_description} + * + * Uses `<atom:content>` or `<content:encoded>` (RSS 1.0 Content Module) + * + * @since 1.0 + * @param bool $content_only Should we avoid falling back to the description? + * @return string|null + */ + public function get_content(bool $content_only = false) + { + if (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'content')) && + ($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_10_content_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) { + return $return; + } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'content')) && + ($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) { + return $return; + } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10_MODULES_CONTENT, 'encoded')) && + ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($tags[0])))) { + return $return; + } elseif (!$content_only) { + return $this->get_description(true); + } + + return null; + } + + /** + * Get the media:thumbnail of the item + * + * Uses `<media:thumbnail>` + * + * + * @return array{url: string, height?: string, width?: string, time?: string}|null + */ + public function get_thumbnail() + { + if (!isset($this->data['thumbnail'])) { + if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'thumbnail')) { + $thumbnail = $return[0]['attribs']['']; + if (empty($thumbnail['url'])) { + $this->data['thumbnail'] = null; + } else { + $thumbnail['url'] = $this->sanitize($thumbnail['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($return[0])); + $this->data['thumbnail'] = $thumbnail; + } + } else { + $this->data['thumbnail'] = null; + } + } + return $this->data['thumbnail']; + } + + /** + * Get a category for the item + * + * @since Beta 3 (previously called `get_categories()` since Beta 2) + * @param int $key The category that you want to return. Remember that arrays begin with 0, not 1 + * @return \SimplePie\Category|null + */ + public function get_category(int $key = 0) + { + $categories = $this->get_categories(); + if (isset($categories[$key])) { + return $categories[$key]; + } + + return null; + } + + /** + * Get all categories for the item + * + * Uses `<atom:category>`, `<category>` or `<dc:subject>` + * + * @since Beta 3 + * @return \SimplePie\Category[]|null List of {@see \SimplePie\Category} objects + */ + public function get_categories() + { + $categories = []; + + $type = 'category'; + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, $type) as $category) { + $term = null; + $scheme = null; + $label = null; + if (isset($category['attribs']['']['term'])) { + $term = $this->sanitize($category['attribs']['']['term'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) { + $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['label'])) { + $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label, $type]); + } + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, $type) as $category) { + // This is really the label, but keep this as the term also for BC. + // Label will also work on retrieving because that falls back to term. + $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + if (isset($category['attribs']['']['domain'])) { + $scheme = $this->sanitize($category['attribs']['']['domain'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $scheme = null; + } + $categories[] = $this->registry->create(Category::class, [$term, $scheme, null, $type]); + } + + $type = 'subject'; + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, $type) as $category) { + $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null, $type]); + } + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, $type) as $category) { + $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null, $type]); + } + + if (!empty($categories)) { + return array_unique($categories); + } + + return null; + } + + /** + * Get an author for the item + * + * @since Beta 2 + * @param int $key The author that you want to return. Remember that arrays begin with 0, not 1 + * @return \SimplePie\Author|null + */ + public function get_author(int $key = 0) + { + $authors = $this->get_authors(); + if (isset($authors[$key])) { + return $authors[$key]; + } + + return null; + } + + /** + * Get a contributor for the item + * + * @since 1.1 + * @param int $key The contrbutor that you want to return. Remember that arrays begin with 0, not 1 + * @return \SimplePie\Author|null + */ + public function get_contributor(int $key = 0) + { + $contributors = $this->get_contributors(); + if (isset($contributors[$key])) { + return $contributors[$key]; + } + + return null; + } + + /** + * Get all contributors for the item + * + * Uses `<atom:contributor>` + * + * @since 1.1 + * @return \SimplePie\Author[]|null List of {@see \SimplePie\Author} objects + */ + public function get_contributors() + { + $contributors = []; + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'contributor') as $contributor) { + $name = null; + $uri = null; + $email = null; + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'])) { + $name = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]['data'])) { + $uri = $contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]; + $uri = $this->sanitize($uri['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($uri)); + } + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'])) { + $email = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) { + $contributors[] = $this->registry->create(Author::class, [$name, $uri, $email]); + } + } + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'contributor') as $contributor) { + $name = null; + $url = null; + $email = null; + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'])) { + $name = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]['data'])) { + $url = $contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]; + $url = $this->sanitize($url['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($url)); + } + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'])) { + $email = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) { + $contributors[] = $this->registry->create(Author::class, [$name, $url, $email]); + } + } + + if (!empty($contributors)) { + return array_unique($contributors); + } + + return null; + } + + /** + * Get all authors for the item + * + * Uses `<atom:author>`, `<author>`, `<dc:creator>` or `<itunes:author>` + * + * @since Beta 2 + * @return \SimplePie\Author[]|null List of {@see \SimplePie\Author} objects + */ + public function get_authors() + { + $authors = []; + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'author') as $author) { + $name = null; + $uri = null; + $email = null; + if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'])) { + $name = $this->sanitize($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]['data'])) { + $uri = $author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]; + $uri = $this->sanitize($uri['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($uri)); + } + if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'])) { + $email = $this->sanitize($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) { + $authors[] = $this->registry->create(Author::class, [$name, $uri, $email]); + } + } + if ($author = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'author')) { + $name = null; + $url = null; + $email = null; + if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'])) { + $name = $this->sanitize($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]['data'])) { + $url = $author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]; + $url = $this->sanitize($url['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($url)); + } + if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'])) { + $email = $this->sanitize($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) { + $authors[] = $this->registry->create(Author::class, [$name, $url, $email]); + } + } + if ($author = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'author')) { + $authors[] = $this->registry->create(Author::class, [null, null, $this->sanitize($author[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)]); + } + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'creator') as $author) { + $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); + } + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'creator') as $author) { + $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); + } + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'author') as $author) { + $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); + } + + if (!empty($authors)) { + return array_unique($authors); + } elseif (($source = $this->get_source()) && ($authors = $source->get_authors())) { + return $authors; + } elseif ($authors = $this->feed->get_authors()) { + return $authors; + } + + return null; + } + + /** + * Get the copyright info for the item + * + * Uses `<atom:rights>` or `<dc:rights>` + * + * @since 1.1 + * @return string|null + */ + public function get_copyright() + { + if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'rights')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'rights')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'rights')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + + return null; + } + + /** + * Get the posting date/time for the item + * + * Uses `<atom:published>`, `<atom:updated>`, `<atom:issued>`, + * `<atom:modified>`, `<pubDate>` or `<dc:date>` + * + * Note: obeys PHP's timezone setting. To get a UTC date/time, use + * {@see get_gmdate} + * + * @since Beta 2 (previously called `get_item_date` since 0.8) + * + * @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data) + * @return ($date_format is 'U' ? ?int : ?string) + */ + public function get_date(string $date_format = 'j F Y, g:i a') + { + if (!isset($this->data['date'])) { + if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'published')) { + $this->data['date']['raw'] = $return[0]['data']; + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'pubDate')) { + $this->data['date']['raw'] = $return[0]['data']; + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'date')) { + $this->data['date']['raw'] = $return[0]['data']; + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'date')) { + $this->data['date']['raw'] = $return[0]['data']; + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'updated')) { + $this->data['date']['raw'] = $return[0]['data']; + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'issued')) { + $this->data['date']['raw'] = $return[0]['data']; + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'created')) { + $this->data['date']['raw'] = $return[0]['data']; + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'modified')) { + $this->data['date']['raw'] = $return[0]['data']; + } + + if (!empty($this->data['date']['raw'])) { + $parser = $this->registry->call(Parse\Date::class, 'get'); + $this->data['date']['parsed'] = $parser->parse($this->data['date']['raw']) ?: null; + } else { + $this->data['date'] = null; + } + } + if ($this->data['date']) { + switch ($date_format) { + case '': + return $this->sanitize($this->data['date']['raw'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + + case 'U': + return $this->data['date']['parsed']; + + default: + return date($date_format, $this->data['date']['parsed']); + } + } + + return null; + } + + /** + * Get the update date/time for the item + * + * Uses `<atom:updated>` + * + * Note: obeys PHP's timezone setting. To get a UTC date/time, use + * {@see get_gmdate} + * + * @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data) + * @return ($date_format is 'U' ? ?int : ?string) + */ + public function get_updated_date(string $date_format = 'j F Y, g:i a') + { + if (!isset($this->data['updated'])) { + if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'updated')) { + $this->data['updated']['raw'] = $return[0]['data']; + } + + if (!empty($this->data['updated']['raw'])) { + $parser = $this->registry->call(Parse\Date::class, 'get'); + $this->data['updated']['parsed'] = $parser->parse($this->data['updated']['raw']) ?: null; + } else { + $this->data['updated'] = null; + } + } + if ($this->data['updated']) { + switch ($date_format) { + case '': + return $this->sanitize($this->data['updated']['raw'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + + case 'U': + return $this->data['updated']['parsed']; + + default: + return date($date_format, $this->data['updated']['parsed']); + } + } + + return null; + } + + /** + * Get the localized posting date/time for the item + * + * Returns the date formatted in the localized language. To display in + * languages other than the server's default, you need to change the locale + * with {@link http://php.net/setlocale setlocale()}. The available + * localizations depend on which ones are installed on your web server. + * + * @since 1.0 + * + * @param string $date_format Supports any PHP date format from {@see http://php.net/strftime} (empty for the raw data) + * @return string|null|false see `strftime` for when this can return `false` + */ + public function get_local_date(string $date_format = '%c') + { + if ($date_format === '') { + if (($raw_date = $this->get_date('')) === null) { + return null; + } + + return $this->sanitize($raw_date, \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif (($date = $this->get_date('U')) !== null && $date !== false) { + return strftime($date_format, $date); + } + + return null; + } + + /** + * Get the posting date/time for the item (UTC time) + * + * @see get_date + * @param string $date_format Supports any PHP date format from {@see http://php.net/date} + * @return string|null + */ + public function get_gmdate(string $date_format = 'j F Y, g:i a') + { + $date = $this->get_date('U'); + if ($date === null) { + return null; + } + + return gmdate($date_format, $date); + } + + /** + * Get the update date/time for the item (UTC time) + * + * @see get_updated_date + * @param string $date_format Supports any PHP date format from {@see http://php.net/date} + * @return string|null + */ + public function get_updated_gmdate(string $date_format = 'j F Y, g:i a') + { + $date = $this->get_updated_date('U'); + if ($date === null) { + return null; + } + + return gmdate($date_format, $date); + } + + /** + * Get the permalink for the item + * + * Returns the first link available with a relationship of "alternate". + * Identical to {@see get_link()} with key 0 + * + * @see get_link + * @since 0.8 + * @return string|null Permalink URL + */ + public function get_permalink() + { + $link = $this->get_link(); + $enclosure = $this->get_enclosure(0); + if ($link !== null) { + return $link; + } elseif ($enclosure !== null) { + return $enclosure->get_link(); + } + + return null; + } + + /** + * Get a single link for the item + * + * @since Beta 3 + * @param int $key The link that you want to return. Remember that arrays begin with 0, not 1 + * @param string $rel The relationship of the link to return + * @return string|null Link URL + */ + public function get_link(int $key = 0, string $rel = 'alternate') + { + $links = $this->get_links($rel); + if ($links && $links[$key] !== null) { + return $links[$key]; + } + + return null; + } + + /** + * Get all links for the item + * + * Uses `<atom:link>`, `<link>` or `<guid>` + * + * @since Beta 2 + * @param string $rel The relationship of links to return + * @return array<string>|null Links found for the item (strings) + */ + public function get_links(string $rel = 'alternate') + { + if (!isset($this->data['links'])) { + $this->data['links'] = []; + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'link') as $link) { + if (isset($link['attribs']['']['href'])) { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($link)); + } + } + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'link') as $link) { + if (isset($link['attribs']['']['href'])) { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($link)); + } + } + if ($links = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'link')) { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($links[0])); + } + if ($links = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'link')) { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($links[0])); + } + if ($links = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'link')) { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($links[0])); + } + if ($links = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'guid')) { + if (!isset($links[0]['attribs']['']['isPermaLink']) || strtolower(trim($links[0]['attribs']['']['isPermaLink'])) === 'true') { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($links[0])); + } + } + + $keys = array_keys($this->data['links']); + foreach ($keys as $key) { + if ($this->registry->call(Misc::class, 'is_isegment_nz_nc', [$key])) { + if (isset($this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key])) { + $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key]); + $this->data['links'][$key] = &$this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key]; + } else { + $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key] = &$this->data['links'][$key]; + } + } elseif (substr((string) $key, 0, 41) === \SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY) { + $this->data['links'][substr((string) $key, 41)] = &$this->data['links'][$key]; + } + $this->data['links'][$key] = array_unique($this->data['links'][$key]); + } + } + if (isset($this->data['links'][$rel])) { + return $this->data['links'][$rel]; + } + + return null; + } + + /** + * Get an enclosure from the item + * + * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS. + * + * @since Beta 2 + * @todo Add ability to prefer one type of content over another (in a media group). + * @param int $key The enclosure that you want to return. Remember that arrays begin with 0, not 1 + * @return \SimplePie\Enclosure|null + */ + public function get_enclosure(int $key = 0) + { + $enclosures = $this->get_enclosures(); + if (isset($enclosures[$key])) { + return $enclosures[$key]; + } + + return null; + } + + /** + * Get all available enclosures (podcasts, etc.) + * + * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS. + * + * At this point, we're pretty much assuming that all enclosures for an item + * are the same content. Anything else is too complicated to + * properly support. + * + * @since Beta 2 + * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4). + * @todo If an element exists at a level, but its value is empty, we should fall back to the value from the parent (if it exists). + * @return \SimplePie\Enclosure[]|null List of \SimplePie\Enclosure items + */ + public function get_enclosures() + { + if (!isset($this->data['enclosures'])) { + $this->data['enclosures'] = []; + + // Elements + $captions_parent = null; + $categories_parent = null; + $copyrights_parent = null; + $credits_parent = null; + $description_parent = null; + $duration_parent = null; + $hashes_parent = null; + $keywords_parent = null; + $player_parent = null; + $ratings_parent = null; + $restrictions_parent = []; + $thumbnails_parent = null; + $title_parent = null; + + // Let's do the channel and item-level ones first, and just re-use them if we need to. + $parent = $this->get_feed(); + + // CAPTIONS + if ($captions = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'text')) { + foreach ($captions as $caption) { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) { + $caption_type = $this->sanitize($caption['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['data'])) { + $caption_text = $this->sanitize($caption['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $captions_parent[] = $this->registry->create(Caption::class, [$caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text]); + } + } elseif ($captions = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'text')) { + foreach ($captions as $caption) { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) { + $caption_type = $this->sanitize($caption['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['data'])) { + $caption_text = $this->sanitize($caption['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $captions_parent[] = $this->registry->create(Caption::class, [$caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text]); + } + } + if (is_array($captions_parent)) { + $captions_parent = array_values(array_unique($captions_parent)); + } + + // CATEGORIES + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'category') as $category) { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) { + $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) { + $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) { + $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $categories_parent[] = $this->registry->create(Category::class, [$term, $scheme, $label]); + } + foreach ((array) $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'category') as $category) { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) { + $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) { + $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) { + $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $categories_parent[] = $this->registry->create(Category::class, [$term, $scheme, $label]); + } + foreach ((array) $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'category') as $category) { + $term = null; + $scheme = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; + $label = null; + if (isset($category['attribs']['']['text'])) { + $label = $this->sanitize($category['attribs']['']['text'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $categories_parent[] = $this->registry->create(Category::class, [$term, $scheme, $label]); + + if (isset($category['child'][\SimplePie\SimplePie::NAMESPACE_ITUNES]['category'])) { + foreach ((array) $category['child'][\SimplePie\SimplePie::NAMESPACE_ITUNES]['category'] as $subcategory) { + if (isset($subcategory['attribs']['']['text'])) { + $label = $this->sanitize($subcategory['attribs']['']['text'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $categories_parent[] = $this->registry->create(Category::class, [$term, $scheme, $label]); + } + } + } + if (is_array($categories_parent)) { + $categories_parent = array_values(array_unique($categories_parent)); + } + + // COPYRIGHT + if ($copyright = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'copyright')) { + $copyright_url = null; + $copyright_label = null; + if (isset($copyright[0]['attribs']['']['url'])) { + $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($copyright[0]['data'])) { + $copyright_label = $this->sanitize($copyright[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $copyrights_parent = $this->registry->create(Copyright::class, [$copyright_url, $copyright_label]); + } elseif ($copyright = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'copyright')) { + $copyright_url = null; + $copyright_label = null; + if (isset($copyright[0]['attribs']['']['url'])) { + $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($copyright[0]['data'])) { + $copyright_label = $this->sanitize($copyright[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $copyrights_parent = $this->registry->create(Copyright::class, [$copyright_url, $copyright_label]); + } + + // CREDITS + if ($credits = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'credit')) { + foreach ($credits as $credit) { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) { + $credit_role = $this->sanitize($credit['attribs']['']['role'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) { + $credit_name = $this->sanitize($credit['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $credits_parent[] = $this->registry->create(Credit::class, [$credit_role, $credit_scheme, $credit_name]); + } + } elseif ($credits = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'credit')) { + foreach ($credits as $credit) { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) { + $credit_role = $this->sanitize($credit['attribs']['']['role'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) { + $credit_name = $this->sanitize($credit['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $credits_parent[] = $this->registry->create(Credit::class, [$credit_role, $credit_scheme, $credit_name]); + } + } + if (is_array($credits_parent)) { + $credits_parent = array_values(array_unique($credits_parent)); + } + + // DESCRIPTION + if ($description_parent = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'description')) { + if (isset($description_parent[0]['data'])) { + $description_parent = $this->sanitize($description_parent[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + } elseif ($description_parent = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'description')) { + if (isset($description_parent[0]['data'])) { + $description_parent = $this->sanitize($description_parent[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + } + + // DURATION + $duration_tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'duration'); + if ($duration_tags !== null) { + $seconds = null; + $minutes = null; + $hours = null; + if (isset($duration_tags[0]['data'])) { + $temp = explode(':', $this->sanitize($duration_tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); + $seconds = (int) array_pop($temp); + if (count($temp) > 0) { + $minutes = (int) array_pop($temp); + $seconds += $minutes * 60; + } + if (count($temp) > 0) { + $hours = (int) array_pop($temp); + $seconds += $hours * 3600; + } + unset($temp); + $duration_parent = $seconds; + } + } + + // HASHES + if ($hashes_iterator = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'hash')) { + foreach ($hashes_iterator as $hash) { + $value = null; + $algo = null; + if (isset($hash['data'])) { + $value = $this->sanitize($hash['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) { + $algo = $this->sanitize($hash['attribs']['']['algo'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $algo = 'md5'; + } + $hashes_parent[] = $algo.':'.$value; + } + } elseif ($hashes_iterator = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'hash')) { + foreach ($hashes_iterator as $hash) { + $value = null; + $algo = null; + if (isset($hash['data'])) { + $value = $this->sanitize($hash['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) { + $algo = $this->sanitize($hash['attribs']['']['algo'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $algo = 'md5'; + } + $hashes_parent[] = $algo.':'.$value; + } + } + if (is_array($hashes_parent)) { + $hashes_parent = array_values(array_unique($hashes_parent)); + } + + // KEYWORDS + if ($keywords = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'keywords')) { + if (isset($keywords[0]['data'])) { + $temp = explode(',', $this->sanitize($keywords[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); + foreach ($temp as $word) { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } elseif ($keywords = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'keywords')) { + if (isset($keywords[0]['data'])) { + $temp = explode(',', $this->sanitize($keywords[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); + foreach ($temp as $word) { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } elseif ($keywords = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'keywords')) { + if (isset($keywords[0]['data'])) { + $temp = explode(',', $this->sanitize($keywords[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); + foreach ($temp as $word) { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } elseif ($keywords = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'keywords')) { + if (isset($keywords[0]['data'])) { + $temp = explode(',', $this->sanitize($keywords[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); + foreach ($temp as $word) { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } + if (is_array($keywords_parent)) { + $keywords_parent = array_values(array_unique($keywords_parent)); + } + + // PLAYER + if ($player_parent = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'player')) { + if (isset($player_parent[0]['attribs']['']['url'])) { + $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($player_parent[0])); + } + } elseif ($player_parent = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'player')) { + if (isset($player_parent[0]['attribs']['']['url'])) { + $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($player_parent[0])); + } + } + + // RATINGS + if ($ratings = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'rating')) { + foreach ($ratings as $rating) { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) { + $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $ratings_parent[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); + } + } elseif ($ratings = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'explicit')) { + foreach ($ratings as $rating) { + $rating_scheme = 'urn:itunes'; + $rating_value = null; + if (isset($rating['data'])) { + $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $ratings_parent[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); + } + } elseif ($ratings = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'rating')) { + foreach ($ratings as $rating) { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) { + $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $ratings_parent[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); + } + } elseif ($ratings = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'explicit')) { + foreach ($ratings as $rating) { + $rating_scheme = 'urn:itunes'; + $rating_value = null; + if (isset($rating['data'])) { + $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $ratings_parent[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); + } + } + if (is_array($ratings_parent)) { + $ratings_parent = array_values(array_unique($ratings_parent)); + } + + // RESTRICTIONS + if ($restrictions = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'restriction')) { + foreach ($restrictions as $restriction) { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) { + $restriction_value = $this->sanitize($restriction['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $restrictions_parent[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); + } + } elseif ($restrictions = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'block')) { + foreach ($restrictions as $restriction) { + $restriction_relationship = Restriction::RELATIONSHIP_ALLOW; + $restriction_type = null; + $restriction_value = 'itunes'; + if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') { + $restriction_relationship = Restriction::RELATIONSHIP_DENY; + } + $restrictions_parent[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); + } + } elseif ($restrictions = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'restriction')) { + foreach ($restrictions as $restriction) { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) { + $restriction_value = $this->sanitize($restriction['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $restrictions_parent[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); + } + } elseif ($restrictions = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'block')) { + foreach ($restrictions as $restriction) { + $restriction_relationship = Restriction::RELATIONSHIP_ALLOW; + $restriction_type = null; + $restriction_value = 'itunes'; + if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') { + $restriction_relationship = Restriction::RELATIONSHIP_DENY; + } + $restrictions_parent[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); + } + } + if (count($restrictions_parent) > 0) { + $restrictions_parent = array_values(array_unique($restrictions_parent)); + } else { + $restrictions_parent = [new \SimplePie\Restriction(Restriction::RELATIONSHIP_ALLOW, null, 'default')]; + } + + // THUMBNAILS + if ($thumbnails = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'thumbnail')) { + foreach ($thumbnails as $thumbnail) { + if (isset($thumbnail['attribs']['']['url'])) { + $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($thumbnail)); + } + } + } elseif ($thumbnails = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'thumbnail')) { + foreach ($thumbnails as $thumbnail) { + if (isset($thumbnail['attribs']['']['url'])) { + $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($thumbnail)); + } + } + } + + // TITLES + if ($title_parent = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'title')) { + if (isset($title_parent[0]['data'])) { + $title_parent = $this->sanitize($title_parent[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + } elseif ($title_parent = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'title')) { + if (isset($title_parent[0]['data'])) { + $title_parent = $this->sanitize($title_parent[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + } + + // Clear the memory + unset($parent); + + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + // Elements + $captions = null; + $categories = null; + $copyrights = null; + $credits = null; + $description = null; + $hashes = null; + $keywords = null; + $player = null; + $ratings = null; + $restrictions = null; + $thumbnails = null; + $title = null; + + // If we have media:group tags, loop through them. + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'group') as $group) { + if (isset($group['child']) && isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content'])) { + // If we have media:content tags, loop through them. + foreach ((array) $group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content'] as $content) { + if (isset($content['attribs']['']['url'])) { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + // Elements + $captions = null; + $categories = null; + $copyrights = null; + $credits = null; + $description = null; + $hashes = null; + $keywords = null; + $player = null; + $ratings = null; + $restrictions = null; + $thumbnails = null; + $title = null; + + // Start checking the attributes of media:content + if (isset($content['attribs']['']['bitrate'])) { + $bitrate = $this->sanitize($content['attribs']['']['bitrate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['channels'])) { + $channels = $this->sanitize($content['attribs']['']['channels'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['duration'])) { + $duration = $this->sanitize($content['attribs']['']['duration'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $duration = $duration_parent; + } + if (isset($content['attribs']['']['expression'])) { + $expression = $this->sanitize($content['attribs']['']['expression'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['framerate'])) { + $framerate = $this->sanitize($content['attribs']['']['framerate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['height'])) { + $height = $this->sanitize($content['attribs']['']['height'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['lang'])) { + $lang = $this->sanitize($content['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['fileSize'])) { + $length = intval($content['attribs']['']['fileSize']); + } + if (isset($content['attribs']['']['medium'])) { + $medium = $this->sanitize($content['attribs']['']['medium'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['samplingrate'])) { + $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['type'])) { + $type = $this->sanitize($content['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['width'])) { + $width = $this->sanitize($content['attribs']['']['width'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $url = $this->sanitize($content['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($content)); + + // Checking the other optional media: elements. Priority: media:content, media:group, item, channel + + // CAPTIONS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'] as $caption) { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) { + $caption_type = $this->sanitize($caption['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['data'])) { + $caption_text = $this->sanitize($caption['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $captions[] = $this->registry->create(Caption::class, [$caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text]); + } + if (is_array($captions)) { + $captions = array_values(array_unique($captions)); + } + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'])) { + foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'] as $caption) { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) { + $caption_type = $this->sanitize($caption['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['data'])) { + $caption_text = $this->sanitize($caption['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $captions[] = $this->registry->create(Caption::class, [$caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text]); + } + if (is_array($captions)) { + $captions = array_values(array_unique($captions)); + } + } else { + $captions = $captions_parent; + } + + // CATEGORIES + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'])) { + foreach ((array) $content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'] as $category) { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) { + $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) { + $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) { + $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]); + } + } + if (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'])) { + foreach ((array) $group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'] as $category) { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) { + $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) { + $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) { + $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]); + } + } + if (is_array($categories) && is_array($categories_parent)) { + $categories = array_values(array_unique(array_merge($categories, $categories_parent))); + } elseif (is_array($categories)) { + $categories = array_values(array_unique($categories)); + } elseif (is_array($categories_parent)) { + $categories = array_values(array_unique($categories_parent)); + } + + // COPYRIGHTS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'])) { + $copyright_url = null; + $copyright_label = null; + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) { + $copyright_url = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'])) { + $copyright_label = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $copyrights = $this->registry->create(Copyright::class, [$copyright_url, $copyright_label]); + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'])) { + $copyright_url = null; + $copyright_label = null; + if (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) { + $copyright_url = $this->sanitize($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'])) { + $copyright_label = $this->sanitize($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $copyrights = $this->registry->create(Copyright::class, [$copyright_url, $copyright_label]); + } else { + $copyrights = $copyrights_parent; + } + + // CREDITS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'] as $credit) { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) { + $credit_role = $this->sanitize($credit['attribs']['']['role'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) { + $credit_name = $this->sanitize($credit['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $credits[] = $this->registry->create(Credit::class, [$credit_role, $credit_scheme, $credit_name]); + } + if (is_array($credits)) { + $credits = array_values(array_unique($credits)); + } + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'])) { + foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'] as $credit) { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) { + $credit_role = $this->sanitize($credit['attribs']['']['role'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) { + $credit_name = $this->sanitize($credit['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $credits[] = $this->registry->create(Credit::class, [$credit_role, $credit_scheme, $credit_name]); + } + if (is_array($credits)) { + $credits = array_values(array_unique($credits)); + } + } else { + $credits = $credits_parent; + } + + // DESCRIPTION + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'])) { + $description = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'])) { + $description = $this->sanitize($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $description = $description_parent; + } + + // HASHES + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'] as $hash) { + $value = null; + $algo = null; + if (isset($hash['data'])) { + $value = $this->sanitize($hash['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) { + $algo = $this->sanitize($hash['attribs']['']['algo'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $algo = 'md5'; + } + $hashes[] = $algo.':'.$value; + } + if (is_array($hashes)) { + $hashes = array_values(array_unique($hashes)); + } + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'])) { + foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'] as $hash) { + $value = null; + $algo = null; + if (isset($hash['data'])) { + $value = $this->sanitize($hash['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) { + $algo = $this->sanitize($hash['attribs']['']['algo'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $algo = 'md5'; + } + $hashes[] = $algo.':'.$value; + } + if (is_array($hashes)) { + $hashes = array_values(array_unique($hashes)); + } + } else { + $hashes = $hashes_parent; + } + + // KEYWORDS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'])) { + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'])) { + $temp = explode(',', $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); + foreach ($temp as $word) { + $keywords[] = trim($word); + } + unset($temp); + } + if (is_array($keywords)) { + $keywords = array_values(array_unique($keywords)); + } + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'])) { + if (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'])) { + $temp = explode(',', $this->sanitize($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); + foreach ($temp as $word) { + $keywords[] = trim($word); + } + unset($temp); + } + if (is_array($keywords)) { + $keywords = array_values(array_unique($keywords)); + } + } else { + $keywords = $keywords_parent; + } + + // PLAYER + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'])) { + $playerElem = $content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'][0]; + $player = $this->sanitize($playerElem['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($playerElem)); + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'])) { + $playerElem = $group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'][0]; + $player = $this->sanitize($playerElem['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($playerElem)); + } else { + $player = $player_parent; + } + + // RATINGS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'] as $rating) { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) { + $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $ratings[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); + } + if (is_array($ratings)) { + $ratings = array_values(array_unique($ratings)); + } + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'])) { + foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'] as $rating) { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) { + $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $ratings[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); + } + if (is_array($ratings)) { + $ratings = array_values(array_unique($ratings)); + } + } else { + $ratings = $ratings_parent; + } + + // RESTRICTIONS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'] as $restriction) { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) { + $restriction_value = $this->sanitize($restriction['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $restrictions[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); + } + if (is_array($restrictions)) { + $restrictions = array_values(array_unique($restrictions)); + } + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'])) { + foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'] as $restriction) { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) { + $restriction_value = $this->sanitize($restriction['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $restrictions[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); + } + if (is_array($restrictions)) { + $restrictions = array_values(array_unique($restrictions)); + } + } else { + $restrictions = $restrictions_parent; + } + + // THUMBNAILS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) { + $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($thumbnail)); + } + if (is_array($thumbnails)) { + $thumbnails = array_values(array_unique($thumbnails)); + } + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'])) { + foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) { + $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($thumbnail)); + } + if (is_array($thumbnails)) { + $thumbnails = array_values(array_unique($thumbnails)); + } + } else { + $thumbnails = $thumbnails_parent; + } + + // TITLES + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'])) { + $title = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'])) { + $title = $this->sanitize($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $title = $title_parent; + } + + $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width]); + } + } + } + } + + // If we have standalone media:content tags, loop through them. + if (isset($this->data['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content'])) { + foreach ((array) $this->data['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content'] as $content) { + if (isset($content['attribs']['']['url']) || isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'])) { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + // Elements + $captions = null; + $categories = null; + $copyrights = null; + $credits = null; + $description = null; + $hashes = null; + $keywords = null; + $player = null; + $ratings = null; + $restrictions = null; + $thumbnails = null; + $title = null; + + // Start checking the attributes of media:content + if (isset($content['attribs']['']['bitrate'])) { + $bitrate = $this->sanitize($content['attribs']['']['bitrate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['channels'])) { + $channels = $this->sanitize($content['attribs']['']['channels'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['duration'])) { + $duration = $this->sanitize($content['attribs']['']['duration'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $duration = $duration_parent; + } + if (isset($content['attribs']['']['expression'])) { + $expression = $this->sanitize($content['attribs']['']['expression'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['framerate'])) { + $framerate = $this->sanitize($content['attribs']['']['framerate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['height'])) { + $height = $this->sanitize($content['attribs']['']['height'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['lang'])) { + $lang = $this->sanitize($content['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['fileSize'])) { + $length = intval($content['attribs']['']['fileSize']); + } + if (isset($content['attribs']['']['medium'])) { + $medium = $this->sanitize($content['attribs']['']['medium'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['samplingrate'])) { + $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['type'])) { + $type = $this->sanitize($content['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['width'])) { + $width = $this->sanitize($content['attribs']['']['width'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['url'])) { + $url = $this->sanitize($content['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($content)); + } + // Checking the other optional media: elements. Priority: media:content, media:group, item, channel + + // CAPTIONS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'] as $caption) { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) { + $caption_type = $this->sanitize($caption['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($caption['data'])) { + $caption_text = $this->sanitize($caption['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $captions[] = $this->registry->create(Caption::class, [$caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text]); + } + if (is_array($captions)) { + $captions = array_values(array_unique($captions)); + } + } else { + $captions = $captions_parent; + } + + // CATEGORIES + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'])) { + foreach ((array) $content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'] as $category) { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) { + $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) { + $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) { + $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]); + } + } + if (is_array($categories) && is_array($categories_parent)) { + $categories = array_values(array_unique(array_merge($categories, $categories_parent))); + } elseif (is_array($categories)) { + $categories = array_values(array_unique($categories)); + } elseif (is_array($categories_parent)) { + $categories = array_values(array_unique($categories_parent)); + } else { + $categories = null; + } + + // COPYRIGHTS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'])) { + $copyright_url = null; + $copyright_label = null; + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) { + $copyright_url = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'])) { + $copyright_label = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $copyrights = $this->registry->create(Copyright::class, [$copyright_url, $copyright_label]); + } else { + $copyrights = $copyrights_parent; + } + + // CREDITS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'] as $credit) { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) { + $credit_role = $this->sanitize($credit['attribs']['']['role'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) { + $credit_name = $this->sanitize($credit['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $credits[] = $this->registry->create(Credit::class, [$credit_role, $credit_scheme, $credit_name]); + } + if (is_array($credits)) { + $credits = array_values(array_unique($credits)); + } + } else { + $credits = $credits_parent; + } + + // DESCRIPTION + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'])) { + $description = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $description = $description_parent; + } + + // HASHES + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'] as $hash) { + $value = null; + $algo = null; + if (isset($hash['data'])) { + $value = $this->sanitize($hash['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) { + $algo = $this->sanitize($hash['attribs']['']['algo'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $algo = 'md5'; + } + $hashes[] = $algo.':'.$value; + } + if (is_array($hashes)) { + $hashes = array_values(array_unique($hashes)); + } + } else { + $hashes = $hashes_parent; + } + + // KEYWORDS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'])) { + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'])) { + $temp = explode(',', $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); + foreach ($temp as $word) { + $keywords[] = trim($word); + } + unset($temp); + } + if (is_array($keywords)) { + $keywords = array_values(array_unique($keywords)); + } + } else { + $keywords = $keywords_parent; + } + + // PLAYER + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'])) { + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'])) { + $playerElem = $content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'][0]; + $player = $this->sanitize($playerElem['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($playerElem)); + } + } else { + $player = $player_parent; + } + + // RATINGS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'] as $rating) { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) { + $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $ratings[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); + } + if (is_array($ratings)) { + $ratings = array_values(array_unique($ratings)); + } + } else { + $ratings = $ratings_parent; + } + + // RESTRICTIONS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'] as $restriction) { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) { + $restriction_value = $this->sanitize($restriction['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $restrictions[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); + } + if (is_array($restrictions)) { + $restrictions = array_values(array_unique($restrictions)); + } + } else { + $restrictions = $restrictions_parent; + } + + // THUMBNAILS + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'])) { + foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) { + if (isset($thumbnail['attribs']['']['url'])) { + $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($thumbnail)); + } + } + if (is_array($thumbnails)) { + $thumbnails = array_values(array_unique($thumbnails)); + } + } else { + $thumbnails = $thumbnails_parent; + } + + // TITLES + if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'])) { + $title = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $title = $title_parent; + } + + $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width]); + } + } + } + + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'link') as $link) { + if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + $url = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($link)); + if (isset($link['attribs']['']['type'])) { + $type = $this->sanitize($link['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($link['attribs']['']['length'])) { + $length = intval($link['attribs']['']['length']); + } + if (isset($link['attribs']['']['title'])) { + $title = $this->sanitize($link['attribs']['']['title'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $title = $title_parent; + } + + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title, $width]); + } + } + + foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'link') as $link) { + if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + $url = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($link)); + if (isset($link['attribs']['']['type'])) { + $type = $this->sanitize($link['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($link['attribs']['']['length'])) { + $length = intval($link['attribs']['']['length']); + } + + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width]); + } + } + + foreach ($this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'enclosure') ?? [] as $enclosure) { + if (isset($enclosure['attribs']['']['url'])) { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + $url = $this->sanitize($enclosure['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($enclosure)); + $url = $this->get_sanitize()->https_url($url); + if (isset($enclosure['attribs']['']['type'])) { + $type = $this->sanitize($enclosure['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($enclosure['attribs']['']['length'])) { + $length = intval($enclosure['attribs']['']['length']); + } + + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width]); + } + } + + if (count($this->data['enclosures']) === 0 && ($url || $type || $length || $bitrate || $captions_parent || $categories_parent || $channels || $copyrights_parent || $credits_parent || $description_parent || $duration_parent || $expression || $framerate || $hashes_parent || $height || $keywords_parent || $lang || $medium || $player_parent || $ratings_parent || $samplingrate || $thumbnails_parent || $title_parent || $width)) { + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width]); + } + + $this->data['enclosures'] = array_values(array_unique($this->data['enclosures'])); + } + if (!empty($this->data['enclosures'])) { + return $this->data['enclosures']; + } + + return null; + } + + /** + * Get the latitude coordinates for the item + * + * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications + * + * Uses `<geo:lat>` or `<georss:point>` + * + * @since 1.0 + * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo + * @link http://www.georss.org/ GeoRSS + * @return float|null + */ + public function get_latitude() + { + if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'lat')) { + return (float) $return[0]['data']; + } elseif (($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { + return (float) $match[1]; + } + + return null; + } + + /** + * Get the longitude coordinates for the item + * + * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications + * + * Uses `<geo:long>`, `<geo:lon>` or `<georss:point>` + * + * @since 1.0 + * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo + * @link http://www.georss.org/ GeoRSS + * @return float|null + */ + public function get_longitude() + { + if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'long')) { + return (float) $return[0]['data']; + } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'lon')) { + return (float) $return[0]['data']; + } elseif (($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { + return (float) $match[2]; + } + + return null; + } + + /** + * Get the `<atom:source>` for the item + * + * @since 1.1 + * @return \SimplePie\Source|null + */ + public function get_source() + { + if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'source')) { + return $this->registry->create(Source::class, [$this, $return[0]]); + } + + return null; + } + + public function set_sanitize(Sanitize $sanitize): void + { + $this->sanitize = $sanitize; + } + + protected function get_sanitize(): Sanitize + { + if ($this->sanitize === null) { + $this->sanitize = new Sanitize(); + } + + return $this->sanitize; + } +} + +class_alias('SimplePie\Item', 'SimplePie_Item'); + +} + +namespace SimplePie { + +use DomDocument; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\UriFactoryInterface; +use SimplePie\HTTP\Client; +use SimplePie\HTTP\ClientException; +use SimplePie\HTTP\FileClient; +use SimplePie\HTTP\Psr18Client; +use SimplePie\HTTP\Response; + +/** + * Used for feed auto-discovery + * + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_locator_class()} + */ +class Locator implements RegistryAware +{ + /** @var ?string */ + public $useragent = null; + /** @var int */ + public $timeout = 10; + /** @var File */ + public $file; + /** @var string[] */ + public $local = []; + /** @var string[] */ + public $elsewhere = []; + /** @var array<mixed> */ + public $cached_entities = []; + /** @var string */ + public $http_base; + /** @var string */ + public $base; + /** @var int */ + public $base_location = 0; + /** @var int */ + public $checked_feeds = 0; + /** @var int */ + public $max_checked_feeds = 10; + /** @var bool */ + public $force_fsockopen = false; + /** @var array<int, mixed> */ + public $curl_options = []; + /** @var ?\DomDocument */ + public $dom; + /** @var ?Registry */ + protected $registry; + + /** + * @var Client|null + */ + private $http_client = null; + + /** + * @param array<int, mixed> $curl_options + */ + public function __construct(File $file, int $timeout = 10, ?string $useragent = null, int $max_checked_feeds = 10, bool $force_fsockopen = false, array $curl_options = []) + { + $this->file = $file; + $this->useragent = $useragent; + $this->timeout = $timeout; + $this->max_checked_feeds = $max_checked_feeds; + $this->force_fsockopen = $force_fsockopen; + $this->curl_options = $curl_options; + + $body = $this->file->get_body_content(); + + if (class_exists('DOMDocument') && $body != '') { + $this->dom = new \DOMDocument(); + + set_error_handler([Misc::class, 'silence_errors']); + try { + $this->dom->loadHTML($body); + } catch (\Throwable $ex) { + $this->dom = null; + } + restore_error_handler(); + } else { + $this->dom = null; + } + } + + /** + * Set a PSR-18 client and PSR-17 factories + * + * Allows you to use your own HTTP client implementations. + */ + final public function set_http_client( + ClientInterface $http_client, + RequestFactoryInterface $request_factory, + UriFactoryInterface $uri_factory + ): void { + $this->http_client = new Psr18Client($http_client, $request_factory, $uri_factory); + } + + /** + * @return void + */ + public function set_registry(\SimplePie\Registry $registry) + { + $this->registry = $registry; + } + + /** + * @param int-mask-of<SimplePie::LOCATOR_*> $type + * @param array<Response>|null $working + * @return Response|null + */ + public function find(int $type = \SimplePie\SimplePie::LOCATOR_ALL, ?array &$working = null) + { + assert($this->registry !== null); + + if ($this->is_feed($this->file)) { + return $this->file; + } + + if (Misc::is_remote_uri($this->file->get_final_requested_uri())) { + $sniffer = $this->registry->create(Content\Type\Sniffer::class, [$this->file]); + if ($sniffer->get_type() !== 'text/html') { + return null; + } + } + + if ($type & ~\SimplePie\SimplePie::LOCATOR_NONE) { + $this->get_base(); + } + + if ($type & \SimplePie\SimplePie::LOCATOR_AUTODISCOVERY && $working = $this->autodiscovery()) { + return $working[0]; + } + + if ($type & (\SimplePie\SimplePie::LOCATOR_LOCAL_EXTENSION | \SimplePie\SimplePie::LOCATOR_LOCAL_BODY | \SimplePie\SimplePie::LOCATOR_REMOTE_EXTENSION | \SimplePie\SimplePie::LOCATOR_REMOTE_BODY) && $this->get_links()) { + if ($type & \SimplePie\SimplePie::LOCATOR_LOCAL_EXTENSION && $working = $this->extension($this->local)) { + return $working[0]; + } + + if ($type & \SimplePie\SimplePie::LOCATOR_LOCAL_BODY && $working = $this->body($this->local)) { + return $working[0]; + } + + if ($type & \SimplePie\SimplePie::LOCATOR_REMOTE_EXTENSION && $working = $this->extension($this->elsewhere)) { + return $working[0]; + } + + if ($type & \SimplePie\SimplePie::LOCATOR_REMOTE_BODY && $working = $this->body($this->elsewhere)) { + return $working[0]; + } + } + return null; + } + + /** + * @return bool + */ + public function is_feed(Response $file, bool $check_html = false) + { + assert($this->registry !== null); + + if (Misc::is_remote_uri($file->get_final_requested_uri())) { + $sniffer = $this->registry->create(Content\Type\Sniffer::class, [$file]); + $sniffed = $sniffer->get_type(); + $mime_types = ['application/rss+xml', 'application/rdf+xml', + 'text/rdf', 'application/atom+xml', 'text/xml', + 'application/xml', 'application/x-rss+xml']; + if ($check_html) { + $mime_types[] = 'text/html'; + } + + return in_array($sniffed, $mime_types); + } elseif (is_file($file->get_final_requested_uri())) { + return true; + } else { + return false; + } + } + + /** + * @return void + */ + public function get_base() + { + assert($this->registry !== null); + + if ($this->dom === null) { + throw new \SimplePie\Exception('DOMDocument not found, unable to use locator'); + } + $this->http_base = $this->file->get_final_requested_uri(); + $this->base = $this->http_base; + $elements = $this->dom->getElementsByTagName('base'); + foreach ($elements as $element) { + if ($element->hasAttribute('href')) { + $base = $this->registry->call(Misc::class, 'absolutize_url', [trim($element->getAttribute('href')), $this->http_base]); + if ($base === false) { + continue; + } + $this->base = $base; + $this->base_location = method_exists($element, 'getLineNo') ? $element->getLineNo() : 0; + break; + } + } + } + + /** + * @return array<Response>|null + */ + public function autodiscovery() + { + $done = []; + $feeds = []; + $feeds = array_merge($feeds, $this->search_elements_by_tag('link', $done, $feeds)); + $feeds = array_merge($feeds, $this->search_elements_by_tag('a', $done, $feeds)); + $feeds = array_merge($feeds, $this->search_elements_by_tag('area', $done, $feeds)); + + if (!empty($feeds)) { + return array_values($feeds); + } + + return null; + } + + /** + * @param string[] $done + * @param array<string, Response> $feeds + * @return array<string, Response> + */ + protected function search_elements_by_tag(string $name, array &$done, array $feeds) + { + assert($this->registry !== null); + + if ($this->dom === null) { + throw new \SimplePie\Exception('DOMDocument not found, unable to use locator'); + } + + $links = $this->dom->getElementsByTagName($name); + foreach ($links as $link) { + if ($this->checked_feeds === $this->max_checked_feeds) { + break; + } + if ($link->hasAttribute('href') && $link->hasAttribute('rel')) { + $rel = array_unique($this->registry->call(Misc::class, 'space_separated_tokens', [strtolower($link->getAttribute('rel'))])); + $line = method_exists($link, 'getLineNo') ? $link->getLineNo() : 1; + + if ($this->base_location < $line) { + $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->base]); + } else { + $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->http_base]); + } + if ($href === false) { + continue; + } + + if (!in_array($href, $done) && in_array('feed', $rel) || (in_array('alternate', $rel) && !in_array('stylesheet', $rel) && $link->hasAttribute('type') && in_array(strtolower($this->registry->call(Misc::class, 'parse_mime', [$link->getAttribute('type')])), ['text/html', 'application/rss+xml', 'application/atom+xml'])) && !isset($feeds[$href])) { + $this->checked_feeds++; + $headers = [ + 'Accept' => SimplePie::DEFAULT_HTTP_ACCEPT_HEADER, + ]; + + try { + $feed = $this->get_http_client()->request(Client::METHOD_GET, $href, $headers); + + if ((!Misc::is_remote_uri($feed->get_final_requested_uri()) || ($feed->get_status_code() === 200 || $feed->get_status_code() > 206 && $feed->get_status_code() < 300)) && $this->is_feed($feed, true)) { + $feeds[$href] = $feed; + } + } catch (ClientException $th) { + // Just mark it as done and continue. + } + } + $done[] = $href; + } + } + + return $feeds; + } + + /** + * @return true|null + */ + public function get_links() + { + assert($this->registry !== null); + + if ($this->dom === null) { + throw new \SimplePie\Exception('DOMDocument not found, unable to use locator'); + } + + $links = $this->dom->getElementsByTagName('a'); + foreach ($links as $link) { + if ($link->hasAttribute('href')) { + $href = trim($link->getAttribute('href')); + $parsed = $this->registry->call(Misc::class, 'parse_url', [$href]); + if ($parsed['scheme'] === '' || preg_match('/^(https?|feed)?$/i', $parsed['scheme'])) { + if (method_exists($link, 'getLineNo') && $this->base_location < $link->getLineNo()) { + $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->base]); + } else { + $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->http_base]); + } + if ($href === false) { + continue; + } + + $current = $this->registry->call(Misc::class, 'parse_url', [$this->file->get_final_requested_uri()]); + + if ($parsed['authority'] === '' || $parsed['authority'] === $current['authority']) { + $this->local[] = $href; + } else { + $this->elsewhere[] = $href; + } + } + } + } + $this->local = array_unique($this->local); + $this->elsewhere = array_unique($this->elsewhere); + if (!empty($this->local) || !empty($this->elsewhere)) { + return true; + } + return null; + } + + /** + * Extracts first `link` element with given `rel` attribute inside the `head` element. + * + * @return string|null + */ + public function get_rel_link(string $rel) + { + assert($this->registry !== null); + + if ($this->dom === null) { + throw new \SimplePie\Exception('DOMDocument not found, unable to use '. + 'locator'); + } + if (!class_exists('DOMXpath')) { + throw new \SimplePie\Exception('DOMXpath not found, unable to use '. + 'get_rel_link'); + } + + $xpath = new \DOMXpath($this->dom); + $query = '(//head)[1]/link[@rel and @href]'; + /** @var \DOMNodeList<\DOMElement> */ + $queryResult = $xpath->query($query); + foreach ($queryResult as $link) { + $href = trim($link->getAttribute('href')); + $parsed = $this->registry->call(Misc::class, 'parse_url', [$href]); + if ($parsed['scheme'] === '' || + preg_match('/^https?$/i', $parsed['scheme'])) { + if (method_exists($link, 'getLineNo') && + $this->base_location < $link->getLineNo()) { + $href = $this->registry->call( + Misc::class, + 'absolutize_url', + [trim($link->getAttribute('href')), $this->base] + ); + } else { + $href = $this->registry->call( + Misc::class, + 'absolutize_url', + [trim($link->getAttribute('href')), $this->http_base] + ); + } + if ($href === false) { + return null; + } + $rel_values = explode(' ', strtolower($link->getAttribute('rel'))); + if (in_array($rel, $rel_values)) { + return $href; + } + } + } + + return null; + } + + /** + * @param string[] $array + * @return array<Response>|null + */ + public function extension(array &$array) + { + foreach ($array as $key => $value) { + if ($this->checked_feeds === $this->max_checked_feeds) { + break; + } + $extension = strrchr($value, '.'); + if ($extension !== false && in_array(strtolower($extension), ['.rss', '.rdf', '.atom', '.xml'])) { + $this->checked_feeds++; + + $headers = [ + 'Accept' => SimplePie::DEFAULT_HTTP_ACCEPT_HEADER, + ]; + + try { + $feed = $this->get_http_client()->request(Client::METHOD_GET, $value, $headers); + + if ((!Misc::is_remote_uri($feed->get_final_requested_uri()) || ($feed->get_status_code() === 200 || $feed->get_status_code() > 206 && $feed->get_status_code() < 300)) && $this->is_feed($feed)) { + return [$feed]; + } + } catch (ClientException $th) { + // Just unset and continue. + } + + unset($array[$key]); + } + } + return null; + } + + /** + * @param string[] $array + * @return array<Response>|null + */ + public function body(array &$array) + { + foreach ($array as $key => $value) { + if ($this->checked_feeds === $this->max_checked_feeds) { + break; + } + if (preg_match('/(feed|rss|rdf|atom|xml)/i', $value)) { + $this->checked_feeds++; + $headers = [ + 'Accept' => SimplePie::DEFAULT_HTTP_ACCEPT_HEADER, + ]; + + try { + $feed = $this->get_http_client()->request(Client::METHOD_GET, $value, $headers); + + if ((!Misc::is_remote_uri($feed->get_final_requested_uri()) || ($feed->get_status_code() === 200 || $feed->get_status_code() > 206 && $feed->get_status_code() < 300)) && $this->is_feed($feed)) { + return [$feed]; + } + } catch (ClientException $th) { + // Just unset and continue. + } + + unset($array[$key]); + } + } + return null; + } + + /** + * Get a HTTP client + */ + private function get_http_client(): Client + { + assert($this->registry !== null); + + if ($this->http_client === null) { + $options = [ + 'timeout' => $this->timeout, + 'redirects' => 5, + 'force_fsockopen' => $this->force_fsockopen, + 'curl_options' => $this->curl_options, + ]; + + if ($this->useragent !== null) { + $options['useragent'] = $this->useragent; + } + + return new FileClient( + $this->registry, + $options + ); + } + + return $this->http_client; + } +} + +class_alias('SimplePie\Locator', 'SimplePie_Locator', false); + +} + +namespace SimplePie { + +use SimplePie\XML\Declaration\Parser; + +/** + * Miscellaneous utilities + */ +class Misc +{ + /** @var int|null */ + private static $SIMPLEPIE_BUILD = null; + + /** + * @return string + */ + public static function time_hms(int $seconds) + { + $time = ''; + + $hours = floor($seconds / 3600); + $remainder = $seconds % 3600; + if ($hours > 0) { + $time .= $hours.':'; + } + + $minutes = floor($remainder / 60); + $seconds = $remainder % 60; + if ($minutes < 10 && $hours > 0) { + $minutes = '0' . $minutes; + } + if ($seconds < 10) { + $seconds = '0' . $seconds; + } + + $time .= $minutes.':'; + $time .= $seconds; + + return $time; + } + + /** + * @return string|false + */ + public static function absolutize_url(string $relative, string $base) + { + $iri = \SimplePie\IRI::absolutize(new \SimplePie\IRI($base), $relative); + if ($iri === false) { + return false; + } + return $iri->get_uri(); + } + + /** + * @internal + */ + public static function is_remote_uri(string $uri): bool + { + return preg_match('/^https?:\/\//i', $uri) === 1; + } + + /** + * Get a HTML/XML element from a HTML string + * + * @deprecated since SimplePie 1.3, use DOMDocument instead (parsing HTML with regex is bad!) + * @param string $realname Element name (including namespace prefix if applicable) + * @param string $string HTML document + * @return array<array{tag: string, self_closing: bool, attribs: array<string, array{data: string}>, content?: string}> + */ + public static function get_element(string $realname, string $string) + { + // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.3, use "DOMDocument" instead.'), \E_USER_DEPRECATED); + + $return = []; + $name = preg_quote($realname, '/'); + if (preg_match_all("/<($name)" . \SimplePie\SimplePie::PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$name>|(\/)?>)/siU", $string, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + for ($i = 0, $total_matches = count($matches); $i < $total_matches; $i++) { + $return[$i]['tag'] = $realname; + $return[$i]['full'] = $matches[$i][0][0]; + $return[$i]['offset'] = $matches[$i][0][1]; + if (strlen($matches[$i][3][0]) <= 2) { + $return[$i]['self_closing'] = true; + } else { + $return[$i]['self_closing'] = false; + $return[$i]['content'] = $matches[$i][4][0]; + } + $return[$i]['attribs'] = []; + if (isset($matches[$i][2][0]) && preg_match_all('/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*)(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"([^"]*)"|\'([^\']*)\'|([^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?/', ' ' . $matches[$i][2][0] . ' ', $attribs, PREG_SET_ORDER)) { + foreach ($attribs as $attrib) { + if (count($attrib) === 2) { + $attrib[2] = $attrib[1]; + } + $return[$i]['attribs'][strtolower($attrib[1])]['data'] = Misc::entities_decode(end($attrib)); + } + } + } + } + return $return; + } + + /** + * @deprecated since SimplePie 1.9.0. If you need it, you can copy the function to your codebase. But you should consider using `DOMDocument` for any DOM wrangling. + * @param array{tag: string, self_closing: bool, attribs: array<string, array{data: string}>, content: string} $element + * @return string + */ + public static function element_implode(array $element) + { + // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.9.'), \E_USER_DEPRECATED); + + $full = "<{$element['tag']}"; + foreach ($element['attribs'] as $key => $value) { + $key = strtolower($key); + $full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"'; + } + if ($element['self_closing']) { + $full .= ' />'; + } else { + $full .= ">{$element['content']}</{$element['tag']}>"; + } + return $full; + } + + /** + * @param string $message + * @param int $level + * @param string $file + * @param int $line + * @return string + */ + public static function error(string $message, int $level, string $file, int $line) + { + if ((error_reporting() & $level) > 0) { + switch ($level) { + case E_USER_ERROR: + $note = 'PHP Error'; + break; + case E_USER_WARNING: + $note = 'PHP Warning'; + break; + case E_USER_NOTICE: + $note = 'PHP Notice'; + break; + default: + $note = 'Unknown Error'; + break; + } + + $log_error = true; + if (!function_exists('error_log')) { + $log_error = false; + } + + $log_file = @ini_get('error_log'); + if (!empty($log_file) && ('syslog' !== $log_file) && !@is_writable($log_file)) { + $log_error = false; + } + + if ($log_error) { + @error_log("$note: $message in $file on line $line", 0); + } + } + + return $message; + } + + /** + * @return string + */ + public static function fix_protocol(string $url, int $http = 1) + { + $url = Misc::normalize_url($url); + $parsed = Misc::parse_url($url); + if ($parsed['scheme'] !== '' && $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https') { + return Misc::fix_protocol(Misc::compress_parse_url('http', $parsed['authority'], $parsed['path'], $parsed['query'], $parsed['fragment']), $http); + } + + if ($parsed['scheme'] === '' && $parsed['authority'] === '' && !file_exists($url)) { + return Misc::fix_protocol(Misc::compress_parse_url('http', $parsed['path'], '', $parsed['query'], $parsed['fragment']), $http); + } + + if ($http === 2 && $parsed['scheme'] !== '') { + return "feed:$url"; + } elseif ($http === 3 && strtolower($parsed['scheme']) === 'http') { + return substr_replace($url, 'podcast', 0, 4); + } elseif ($http === 4 && strtolower($parsed['scheme']) === 'http') { + return substr_replace($url, 'itpc', 0, 4); + } + + return $url; + } + + /** + * @deprecated since SimplePie 1.8.0, use PHP native array_replace_recursive() instead. + * @param array<mixed> $array1 + * @param array<mixed> $array2 + * @return array<mixed> + */ + public static function array_merge_recursive(array $array1, array $array2) + { + foreach ($array2 as $key => $value) { + if (is_array($value)) { + $array1[$key] = Misc::array_merge_recursive($array1[$key], $value); + } else { + $array1[$key] = $value; + } + } + + return $array1; + } + + /** + * @return array<string, string> + */ + public static function parse_url(string $url) + { + $iri = new \SimplePie\IRI($url); + return [ + 'scheme' => (string) $iri->scheme, + 'authority' => (string) $iri->authority, + 'path' => (string) $iri->path, + 'query' => (string) $iri->query, + 'fragment' => (string) $iri->fragment + ]; + } + + /** + * @return string + */ + public static function compress_parse_url(string $scheme = '', string $authority = '', string $path = '', string $query = '', ?string $fragment = '') + { + $iri = new \SimplePie\IRI(''); + $iri->scheme = $scheme; + $iri->authority = $authority; + $iri->path = $path; + $iri->query = $query; + $iri->fragment = $fragment; + return $iri->get_uri(); + } + + /** + * @return string + */ + public static function normalize_url(string $url) + { + $iri = new \SimplePie\IRI($url); + return $iri->get_uri(); + } + + /** + * @deprecated since SimplePie 1.9.0. This functionality is part of `IRI` – if you need it standalone, consider copying the function to your codebase. + * @param array<int, string> $match + * @return string + */ + public static function percent_encoding_normalization(array $match) + { + $integer = hexdec($match[1]); + if ($integer >= 0x41 && $integer <= 0x5A || $integer >= 0x61 && $integer <= 0x7A || $integer >= 0x30 && $integer <= 0x39 || $integer === 0x2D || $integer === 0x2E || $integer === 0x5F || $integer === 0x7E) { + // Cast for PHPStan, the value would only be float when above PHP_INT_MAX, which would not go in this branch. + return chr((int) $integer); + } + + return strtoupper($match[0]); + } + + /** + * Converts a Windows-1252 encoded string to a UTF-8 encoded string + * + * @static + * @param string $string Windows-1252 encoded string + * @return string UTF-8 encoded string + */ + public static function windows_1252_to_utf8(string $string) + { + static $convert_table = ["\x80" => "\xE2\x82\xAC", "\x81" => "\xEF\xBF\xBD", "\x82" => "\xE2\x80\x9A", "\x83" => "\xC6\x92", "\x84" => "\xE2\x80\x9E", "\x85" => "\xE2\x80\xA6", "\x86" => "\xE2\x80\xA0", "\x87" => "\xE2\x80\xA1", "\x88" => "\xCB\x86", "\x89" => "\xE2\x80\xB0", "\x8A" => "\xC5\xA0", "\x8B" => "\xE2\x80\xB9", "\x8C" => "\xC5\x92", "\x8D" => "\xEF\xBF\xBD", "\x8E" => "\xC5\xBD", "\x8F" => "\xEF\xBF\xBD", "\x90" => "\xEF\xBF\xBD", "\x91" => "\xE2\x80\x98", "\x92" => "\xE2\x80\x99", "\x93" => "\xE2\x80\x9C", "\x94" => "\xE2\x80\x9D", "\x95" => "\xE2\x80\xA2", "\x96" => "\xE2\x80\x93", "\x97" => "\xE2\x80\x94", "\x98" => "\xCB\x9C", "\x99" => "\xE2\x84\xA2", "\x9A" => "\xC5\xA1", "\x9B" => "\xE2\x80\xBA", "\x9C" => "\xC5\x93", "\x9D" => "\xEF\xBF\xBD", "\x9E" => "\xC5\xBE", "\x9F" => "\xC5\xB8", "\xA0" => "\xC2\xA0", "\xA1" => "\xC2\xA1", "\xA2" => "\xC2\xA2", "\xA3" => "\xC2\xA3", "\xA4" => "\xC2\xA4", "\xA5" => "\xC2\xA5", "\xA6" => "\xC2\xA6", "\xA7" => "\xC2\xA7", "\xA8" => "\xC2\xA8", "\xA9" => "\xC2\xA9", "\xAA" => "\xC2\xAA", "\xAB" => "\xC2\xAB", "\xAC" => "\xC2\xAC", "\xAD" => "\xC2\xAD", "\xAE" => "\xC2\xAE", "\xAF" => "\xC2\xAF", "\xB0" => "\xC2\xB0", "\xB1" => "\xC2\xB1", "\xB2" => "\xC2\xB2", "\xB3" => "\xC2\xB3", "\xB4" => "\xC2\xB4", "\xB5" => "\xC2\xB5", "\xB6" => "\xC2\xB6", "\xB7" => "\xC2\xB7", "\xB8" => "\xC2\xB8", "\xB9" => "\xC2\xB9", "\xBA" => "\xC2\xBA", "\xBB" => "\xC2\xBB", "\xBC" => "\xC2\xBC", "\xBD" => "\xC2\xBD", "\xBE" => "\xC2\xBE", "\xBF" => "\xC2\xBF", "\xC0" => "\xC3\x80", "\xC1" => "\xC3\x81", "\xC2" => "\xC3\x82", "\xC3" => "\xC3\x83", "\xC4" => "\xC3\x84", "\xC5" => "\xC3\x85", "\xC6" => "\xC3\x86", "\xC7" => "\xC3\x87", "\xC8" => "\xC3\x88", "\xC9" => "\xC3\x89", "\xCA" => "\xC3\x8A", "\xCB" => "\xC3\x8B", "\xCC" => "\xC3\x8C", "\xCD" => "\xC3\x8D", "\xCE" => "\xC3\x8E", "\xCF" => "\xC3\x8F", "\xD0" => "\xC3\x90", "\xD1" => "\xC3\x91", "\xD2" => "\xC3\x92", "\xD3" => "\xC3\x93", "\xD4" => "\xC3\x94", "\xD5" => "\xC3\x95", "\xD6" => "\xC3\x96", "\xD7" => "\xC3\x97", "\xD8" => "\xC3\x98", "\xD9" => "\xC3\x99", "\xDA" => "\xC3\x9A", "\xDB" => "\xC3\x9B", "\xDC" => "\xC3\x9C", "\xDD" => "\xC3\x9D", "\xDE" => "\xC3\x9E", "\xDF" => "\xC3\x9F", "\xE0" => "\xC3\xA0", "\xE1" => "\xC3\xA1", "\xE2" => "\xC3\xA2", "\xE3" => "\xC3\xA3", "\xE4" => "\xC3\xA4", "\xE5" => "\xC3\xA5", "\xE6" => "\xC3\xA6", "\xE7" => "\xC3\xA7", "\xE8" => "\xC3\xA8", "\xE9" => "\xC3\xA9", "\xEA" => "\xC3\xAA", "\xEB" => "\xC3\xAB", "\xEC" => "\xC3\xAC", "\xED" => "\xC3\xAD", "\xEE" => "\xC3\xAE", "\xEF" => "\xC3\xAF", "\xF0" => "\xC3\xB0", "\xF1" => "\xC3\xB1", "\xF2" => "\xC3\xB2", "\xF3" => "\xC3\xB3", "\xF4" => "\xC3\xB4", "\xF5" => "\xC3\xB5", "\xF6" => "\xC3\xB6", "\xF7" => "\xC3\xB7", "\xF8" => "\xC3\xB8", "\xF9" => "\xC3\xB9", "\xFA" => "\xC3\xBA", "\xFB" => "\xC3\xBB", "\xFC" => "\xC3\xBC", "\xFD" => "\xC3\xBD", "\xFE" => "\xC3\xBE", "\xFF" => "\xC3\xBF"]; + + return strtr($string, $convert_table); + } + + /** + * Change a string from one encoding to another + * + * @param string $data Raw data in $input encoding + * @param string $input Encoding of $data + * @param string $output Encoding you want + * @return string|false False if we can't convert it + */ + public static function change_encoding(string $data, string $input, string $output) + { + $input = Misc::encoding($input); + $output = Misc::encoding($output); + + // We fail to fail on non US-ASCII bytes + if ($input === 'US-ASCII') { + static $non_ascii_octets = ''; + if (!$non_ascii_octets) { + for ($i = 0x80; $i <= 0xFF; $i++) { + $non_ascii_octets .= chr($i); + } + } + $data = substr($data, 0, strcspn($data, $non_ascii_octets)); + } + + // This is first, as behaviour of this is completely predictable + if ($input === 'windows-1252' && $output === 'UTF-8') { + return Misc::windows_1252_to_utf8($data); + } + // This is second, as behaviour of this varies only with PHP version (the middle part of this expression checks the encoding is supported). + elseif (function_exists('mb_convert_encoding') && ($return = Misc::change_encoding_mbstring($data, $input, $output))) { + return $return; + } + // This is third, as behaviour of this varies with OS userland and PHP version + elseif (function_exists('iconv') && ($return = Misc::change_encoding_iconv($data, $input, $output))) { + return $return; + } + // This is last, as behaviour of this varies with OS userland and PHP version + elseif (class_exists('\UConverter') && ($return = Misc::change_encoding_uconverter($data, $input, $output))) { + return $return; + } + + // If we can't do anything, just fail + return false; + } + + /** + * @return string|false + */ + protected static function change_encoding_mbstring(string $data, string $input, string $output) + { + if ($input === 'windows-949') { + $input = 'EUC-KR'; + } + if ($output === 'windows-949') { + $output = 'EUC-KR'; + } + if ($input === 'Windows-31J') { + $input = 'SJIS'; + } + if ($output === 'Windows-31J') { + $output = 'SJIS'; + } + + // Check that the encoding is supported + if (!in_array($input, mb_list_encodings())) { + return false; + } + + if (@mb_convert_encoding("\x80", 'UTF-16BE', $input) === "\x00\x80") { + return false; + } + + // Let's do some conversion + if ($return = @mb_convert_encoding($data, $output, $input)) { + return $return; + } + + return false; + } + + /** + * @return string|false + */ + protected static function change_encoding_iconv(string $data, string $input, string $output) + { + return @iconv($input, $output, $data); + } + + /** + * @return string|false + */ + protected static function change_encoding_uconverter(string $data, string $input, string $output) + { + return @\UConverter::transcode($data, $output, $input); + } + + /** + * Normalize an encoding name + * + * This is automatically generated by create.php + * + * To generate it, run `php create.php` on the command line, and copy the + * output to replace this function. + * + * @param string $charset Character set to standardise + * @return string Standardised name + */ + public static function encoding(string $charset) + { + // Normalization from UTS #22 + // Cast for PHPStan, the regex should not fail. + switch (strtolower((string) preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset))) { + case 'adobestandardencoding': + case 'csadobestandardencoding': + return 'Adobe-Standard-Encoding'; + + case 'adobesymbolencoding': + case 'cshppsmath': + return 'Adobe-Symbol-Encoding'; + + case 'ami1251': + case 'amiga1251': + return 'Amiga-1251'; + + case 'ansix31101983': + case 'csat5001983': + case 'csiso99naplps': + case 'isoir99': + case 'naplps': + return 'ANSI_X3.110-1983'; + + case 'arabic7': + case 'asmo449': + case 'csiso89asmo449': + case 'iso9036': + case 'isoir89': + return 'ASMO_449'; + + case 'big5': + case 'csbig5': + return 'Big5'; + + case 'big5hkscs': + return 'Big5-HKSCS'; + + case 'bocu1': + case 'csbocu1': + return 'BOCU-1'; + + case 'brf': + case 'csbrf': + return 'BRF'; + + case 'bs4730': + case 'csiso4unitedkingdom': + case 'gb': + case 'iso646gb': + case 'isoir4': + case 'uk': + return 'BS_4730'; + + case 'bsviewdata': + case 'csiso47bsviewdata': + case 'isoir47': + return 'BS_viewdata'; + + case 'cesu8': + case 'cscesu8': + return 'CESU-8'; + + case 'ca': + case 'csa71': + case 'csaz243419851': + case 'csiso121canadian1': + case 'iso646ca': + case 'isoir121': + return 'CSA_Z243.4-1985-1'; + + case 'csa72': + case 'csaz243419852': + case 'csiso122canadian2': + case 'iso646ca2': + case 'isoir122': + return 'CSA_Z243.4-1985-2'; + + case 'csaz24341985gr': + case 'csiso123csaz24341985gr': + case 'isoir123': + return 'CSA_Z243.4-1985-gr'; + + case 'csiso139csn369103': + case 'csn369103': + case 'isoir139': + return 'CSN_369103'; + + case 'csdecmcs': + case 'dec': + case 'decmcs': + return 'DEC-MCS'; + + case 'csiso21german': + case 'de': + case 'din66003': + case 'iso646de': + case 'isoir21': + return 'DIN_66003'; + + case 'csdkus': + case 'dkus': + return 'dk-us'; + + case 'csiso646danish': + case 'dk': + case 'ds2089': + case 'iso646dk': + return 'DS_2089'; + + case 'csibmebcdicatde': + case 'ebcdicatde': + return 'EBCDIC-AT-DE'; + + case 'csebcdicatdea': + case 'ebcdicatdea': + return 'EBCDIC-AT-DE-A'; + + case 'csebcdiccafr': + case 'ebcdiccafr': + return 'EBCDIC-CA-FR'; + + case 'csebcdicdkno': + case 'ebcdicdkno': + return 'EBCDIC-DK-NO'; + + case 'csebcdicdknoa': + case 'ebcdicdknoa': + return 'EBCDIC-DK-NO-A'; + + case 'csebcdices': + case 'ebcdices': + return 'EBCDIC-ES'; + + case 'csebcdicesa': + case 'ebcdicesa': + return 'EBCDIC-ES-A'; + + case 'csebcdicess': + case 'ebcdicess': + return 'EBCDIC-ES-S'; + + case 'csebcdicfise': + case 'ebcdicfise': + return 'EBCDIC-FI-SE'; + + case 'csebcdicfisea': + case 'ebcdicfisea': + return 'EBCDIC-FI-SE-A'; + + case 'csebcdicfr': + case 'ebcdicfr': + return 'EBCDIC-FR'; + + case 'csebcdicit': + case 'ebcdicit': + return 'EBCDIC-IT'; + + case 'csebcdicpt': + case 'ebcdicpt': + return 'EBCDIC-PT'; + + case 'csebcdicuk': + case 'ebcdicuk': + return 'EBCDIC-UK'; + + case 'csebcdicus': + case 'ebcdicus': + return 'EBCDIC-US'; + + case 'csiso111ecmacyrillic': + case 'ecmacyrillic': + case 'isoir111': + case 'koi8e': + return 'ECMA-cyrillic'; + + case 'csiso17spanish': + case 'es': + case 'iso646es': + case 'isoir17': + return 'ES'; + + case 'csiso85spanish2': + case 'es2': + case 'iso646es2': + case 'isoir85': + return 'ES2'; + + case 'cseucpkdfmtjapanese': + case 'eucjp': + case 'extendedunixcodepackedformatforjapanese': + return 'EUC-JP'; + + case 'cseucfixwidjapanese': + case 'extendedunixcodefixedwidthforjapanese': + return 'Extended_UNIX_Code_Fixed_Width_for_Japanese'; + + case 'gb18030': + return 'GB18030'; + + case 'chinese': + case 'cp936': + case 'csgb2312': + case 'csiso58gb231280': + case 'gb2312': + case 'gb231280': + case 'gbk': + case 'isoir58': + case 'ms936': + case 'windows936': + return 'GBK'; + + case 'cn': + case 'csiso57gb1988': + case 'gb198880': + case 'iso646cn': + case 'isoir57': + return 'GB_1988-80'; + + case 'csiso153gost1976874': + case 'gost1976874': + case 'isoir153': + case 'stsev35888': + return 'GOST_19768-74'; + + case 'csiso150': + case 'csiso150greekccitt': + case 'greekccitt': + case 'isoir150': + return 'greek-ccitt'; + + case 'csiso88greek7': + case 'greek7': + case 'isoir88': + return 'greek7'; + + case 'csiso18greek7old': + case 'greek7old': + case 'isoir18': + return 'greek7-old'; + + case 'cshpdesktop': + case 'hpdesktop': + return 'HP-DeskTop'; + + case 'cshplegal': + case 'hplegal': + return 'HP-Legal'; + + case 'cshpmath8': + case 'hpmath8': + return 'HP-Math8'; + + case 'cshppifont': + case 'hppifont': + return 'HP-Pi-font'; + + case 'cshproman8': + case 'hproman8': + case 'r8': + case 'roman8': + return 'hp-roman8'; + + case 'hzgb2312': + return 'HZ-GB-2312'; + + case 'csibmsymbols': + case 'ibmsymbols': + return 'IBM-Symbols'; + + case 'csibmthai': + case 'ibmthai': + return 'IBM-Thai'; + + case 'cp37': + case 'csibm37': + case 'ebcdiccpca': + case 'ebcdiccpnl': + case 'ebcdiccpus': + case 'ebcdiccpwt': + case 'ibm37': + return 'IBM037'; + + case 'cp38': + case 'csibm38': + case 'ebcdicint': + case 'ibm38': + return 'IBM038'; + + case 'cp273': + case 'csibm273': + case 'ibm273': + return 'IBM273'; + + case 'cp274': + case 'csibm274': + case 'ebcdicbe': + case 'ibm274': + return 'IBM274'; + + case 'cp275': + case 'csibm275': + case 'ebcdicbr': + case 'ibm275': + return 'IBM275'; + + case 'csibm277': + case 'ebcdiccpdk': + case 'ebcdiccpno': + case 'ibm277': + return 'IBM277'; + + case 'cp278': + case 'csibm278': + case 'ebcdiccpfi': + case 'ebcdiccpse': + case 'ibm278': + return 'IBM278'; + + case 'cp280': + case 'csibm280': + case 'ebcdiccpit': + case 'ibm280': + return 'IBM280'; + + case 'cp281': + case 'csibm281': + case 'ebcdicjpe': + case 'ibm281': + return 'IBM281'; + + case 'cp284': + case 'csibm284': + case 'ebcdiccpes': + case 'ibm284': + return 'IBM284'; + + case 'cp285': + case 'csibm285': + case 'ebcdiccpgb': + case 'ibm285': + return 'IBM285'; + + case 'cp290': + case 'csibm290': + case 'ebcdicjpkana': + case 'ibm290': + return 'IBM290'; + + case 'cp297': + case 'csibm297': + case 'ebcdiccpfr': + case 'ibm297': + return 'IBM297'; + + case 'cp420': + case 'csibm420': + case 'ebcdiccpar1': + case 'ibm420': + return 'IBM420'; + + case 'cp423': + case 'csibm423': + case 'ebcdiccpgr': + case 'ibm423': + return 'IBM423'; + + case 'cp424': + case 'csibm424': + case 'ebcdiccphe': + case 'ibm424': + return 'IBM424'; + + case '437': + case 'cp437': + case 'cspc8codepage437': + case 'ibm437': + return 'IBM437'; + + case 'cp500': + case 'csibm500': + case 'ebcdiccpbe': + case 'ebcdiccpch': + case 'ibm500': + return 'IBM500'; + + case 'cp775': + case 'cspc775baltic': + case 'ibm775': + return 'IBM775'; + + case '850': + case 'cp850': + case 'cspc850multilingual': + case 'ibm850': + return 'IBM850'; + + case '851': + case 'cp851': + case 'csibm851': + case 'ibm851': + return 'IBM851'; + + case '852': + case 'cp852': + case 'cspcp852': + case 'ibm852': + return 'IBM852'; + + case '855': + case 'cp855': + case 'csibm855': + case 'ibm855': + return 'IBM855'; + + case '857': + case 'cp857': + case 'csibm857': + case 'ibm857': + return 'IBM857'; + + case 'ccsid858': + case 'cp858': + case 'ibm858': + case 'pcmultilingual850euro': + return 'IBM00858'; + + case '860': + case 'cp860': + case 'csibm860': + case 'ibm860': + return 'IBM860'; + + case '861': + case 'cp861': + case 'cpis': + case 'csibm861': + case 'ibm861': + return 'IBM861'; + + case '862': + case 'cp862': + case 'cspc862latinhebrew': + case 'ibm862': + return 'IBM862'; + + case '863': + case 'cp863': + case 'csibm863': + case 'ibm863': + return 'IBM863'; + + case 'cp864': + case 'csibm864': + case 'ibm864': + return 'IBM864'; + + case '865': + case 'cp865': + case 'csibm865': + case 'ibm865': + return 'IBM865'; + + case '866': + case 'cp866': + case 'csibm866': + case 'ibm866': + return 'IBM866'; + + case 'cp868': + case 'cpar': + case 'csibm868': + case 'ibm868': + return 'IBM868'; + + case '869': + case 'cp869': + case 'cpgr': + case 'csibm869': + case 'ibm869': + return 'IBM869'; + + case 'cp870': + case 'csibm870': + case 'ebcdiccproece': + case 'ebcdiccpyu': + case 'ibm870': + return 'IBM870'; + + case 'cp871': + case 'csibm871': + case 'ebcdiccpis': + case 'ibm871': + return 'IBM871'; + + case 'cp880': + case 'csibm880': + case 'ebcdiccyrillic': + case 'ibm880': + return 'IBM880'; + + case 'cp891': + case 'csibm891': + case 'ibm891': + return 'IBM891'; + + case 'cp903': + case 'csibm903': + case 'ibm903': + return 'IBM903'; + + case '904': + case 'cp904': + case 'csibbm904': + case 'ibm904': + return 'IBM904'; + + case 'cp905': + case 'csibm905': + case 'ebcdiccptr': + case 'ibm905': + return 'IBM905'; + + case 'cp918': + case 'csibm918': + case 'ebcdiccpar2': + case 'ibm918': + return 'IBM918'; + + case 'ccsid924': + case 'cp924': + case 'ebcdiclatin9euro': + case 'ibm924': + return 'IBM00924'; + + case 'cp1026': + case 'csibm1026': + case 'ibm1026': + return 'IBM1026'; + + case 'ibm1047': + return 'IBM1047'; + + case 'ccsid1140': + case 'cp1140': + case 'ebcdicus37euro': + case 'ibm1140': + return 'IBM01140'; + + case 'ccsid1141': + case 'cp1141': + case 'ebcdicde273euro': + case 'ibm1141': + return 'IBM01141'; + + case 'ccsid1142': + case 'cp1142': + case 'ebcdicdk277euro': + case 'ebcdicno277euro': + case 'ibm1142': + return 'IBM01142'; + + case 'ccsid1143': + case 'cp1143': + case 'ebcdicfi278euro': + case 'ebcdicse278euro': + case 'ibm1143': + return 'IBM01143'; + + case 'ccsid1144': + case 'cp1144': + case 'ebcdicit280euro': + case 'ibm1144': + return 'IBM01144'; + + case 'ccsid1145': + case 'cp1145': + case 'ebcdices284euro': + case 'ibm1145': + return 'IBM01145'; + + case 'ccsid1146': + case 'cp1146': + case 'ebcdicgb285euro': + case 'ibm1146': + return 'IBM01146'; + + case 'ccsid1147': + case 'cp1147': + case 'ebcdicfr297euro': + case 'ibm1147': + return 'IBM01147'; + + case 'ccsid1148': + case 'cp1148': + case 'ebcdicinternational500euro': + case 'ibm1148': + return 'IBM01148'; + + case 'ccsid1149': + case 'cp1149': + case 'ebcdicis871euro': + case 'ibm1149': + return 'IBM01149'; + + case 'csiso143iecp271': + case 'iecp271': + case 'isoir143': + return 'IEC_P27-1'; + + case 'csiso49inis': + case 'inis': + case 'isoir49': + return 'INIS'; + + case 'csiso50inis8': + case 'inis8': + case 'isoir50': + return 'INIS-8'; + + case 'csiso51iniscyrillic': + case 'iniscyrillic': + case 'isoir51': + return 'INIS-cyrillic'; + + case 'csinvariant': + case 'invariant': + return 'INVARIANT'; + + case 'iso2022cn': + return 'ISO-2022-CN'; + + case 'iso2022cnext': + return 'ISO-2022-CN-EXT'; + + case 'csiso2022jp': + case 'iso2022jp': + return 'ISO-2022-JP'; + + case 'csiso2022jp2': + case 'iso2022jp2': + return 'ISO-2022-JP-2'; + + case 'csiso2022kr': + case 'iso2022kr': + return 'ISO-2022-KR'; + + case 'cswindows30latin1': + case 'iso88591windows30latin1': + return 'ISO-8859-1-Windows-3.0-Latin-1'; + + case 'cswindows31latin1': + case 'iso88591windows31latin1': + return 'ISO-8859-1-Windows-3.1-Latin-1'; + + case 'csisolatin2': + case 'iso88592': + case 'iso885921987': + case 'isoir101': + case 'l2': + case 'latin2': + return 'ISO-8859-2'; + + case 'cswindows31latin2': + case 'iso88592windowslatin2': + return 'ISO-8859-2-Windows-Latin-2'; + + case 'csisolatin3': + case 'iso88593': + case 'iso885931988': + case 'isoir109': + case 'l3': + case 'latin3': + return 'ISO-8859-3'; + + case 'csisolatin4': + case 'iso88594': + case 'iso885941988': + case 'isoir110': + case 'l4': + case 'latin4': + return 'ISO-8859-4'; + + case 'csisolatincyrillic': + case 'cyrillic': + case 'iso88595': + case 'iso885951988': + case 'isoir144': + return 'ISO-8859-5'; + + case 'arabic': + case 'asmo708': + case 'csisolatinarabic': + case 'ecma114': + case 'iso88596': + case 'iso885961987': + case 'isoir127': + return 'ISO-8859-6'; + + case 'csiso88596e': + case 'iso88596e': + return 'ISO-8859-6-E'; + + case 'csiso88596i': + case 'iso88596i': + return 'ISO-8859-6-I'; + + case 'csisolatingreek': + case 'ecma118': + case 'elot928': + case 'greek': + case 'greek8': + case 'iso88597': + case 'iso885971987': + case 'isoir126': + return 'ISO-8859-7'; + + case 'csisolatinhebrew': + case 'hebrew': + case 'iso88598': + case 'iso885981988': + case 'isoir138': + return 'ISO-8859-8'; + + case 'csiso88598e': + case 'iso88598e': + return 'ISO-8859-8-E'; + + case 'csiso88598i': + case 'iso88598i': + return 'ISO-8859-8-I'; + + case 'cswindows31latin5': + case 'iso88599windowslatin5': + return 'ISO-8859-9-Windows-Latin-5'; + + case 'csisolatin6': + case 'iso885910': + case 'iso8859101992': + case 'isoir157': + case 'l6': + case 'latin6': + return 'ISO-8859-10'; + + case 'iso885913': + return 'ISO-8859-13'; + + case 'iso885914': + case 'iso8859141998': + case 'isoceltic': + case 'isoir199': + case 'l8': + case 'latin8': + return 'ISO-8859-14'; + + case 'iso885915': + case 'latin9': + return 'ISO-8859-15'; + + case 'iso885916': + case 'iso8859162001': + case 'isoir226': + case 'l10': + case 'latin10': + return 'ISO-8859-16'; + + case 'iso10646j1': + return 'ISO-10646-J-1'; + + case 'csunicode': + case 'iso10646ucs2': + return 'ISO-10646-UCS-2'; + + case 'csucs4': + case 'iso10646ucs4': + return 'ISO-10646-UCS-4'; + + case 'csunicodeascii': + case 'iso10646ucsbasic': + return 'ISO-10646-UCS-Basic'; + + case 'csunicodelatin1': + case 'iso10646': + case 'iso10646unicodelatin1': + return 'ISO-10646-Unicode-Latin1'; + + case 'csiso10646utf1': + case 'iso10646utf1': + return 'ISO-10646-UTF-1'; + + case 'csiso115481': + case 'iso115481': + case 'isotr115481': + return 'ISO-11548-1'; + + case 'csiso90': + case 'isoir90': + return 'iso-ir-90'; + + case 'csunicodeibm1261': + case 'isounicodeibm1261': + return 'ISO-Unicode-IBM-1261'; + + case 'csunicodeibm1264': + case 'isounicodeibm1264': + return 'ISO-Unicode-IBM-1264'; + + case 'csunicodeibm1265': + case 'isounicodeibm1265': + return 'ISO-Unicode-IBM-1265'; + + case 'csunicodeibm1268': + case 'isounicodeibm1268': + return 'ISO-Unicode-IBM-1268'; + + case 'csunicodeibm1276': + case 'isounicodeibm1276': + return 'ISO-Unicode-IBM-1276'; + + case 'csiso646basic1983': + case 'iso646basic1983': + case 'ref': + return 'ISO_646.basic:1983'; + + case 'csiso2intlrefversion': + case 'irv': + case 'iso646irv1983': + case 'isoir2': + return 'ISO_646.irv:1983'; + + case 'csiso2033': + case 'e13b': + case 'iso20331983': + case 'isoir98': + return 'ISO_2033-1983'; + + case 'csiso5427cyrillic': + case 'iso5427': + case 'isoir37': + return 'ISO_5427'; + + case 'iso5427cyrillic1981': + case 'iso54271981': + case 'isoir54': + return 'ISO_5427:1981'; + + case 'csiso5428greek': + case 'iso54281980': + case 'isoir55': + return 'ISO_5428:1980'; + + case 'csiso6937add': + case 'iso6937225': + case 'isoir152': + return 'ISO_6937-2-25'; + + case 'csisotextcomm': + case 'iso69372add': + case 'isoir142': + return 'ISO_6937-2-add'; + + case 'csiso8859supp': + case 'iso8859supp': + case 'isoir154': + case 'latin125': + return 'ISO_8859-supp'; + + case 'csiso10367box': + case 'iso10367box': + case 'isoir155': + return 'ISO_10367-box'; + + case 'csiso15italian': + case 'iso646it': + case 'isoir15': + case 'it': + return 'IT'; + + case 'csiso13jisc6220jp': + case 'isoir13': + case 'jisc62201969': + case 'jisc62201969jp': + case 'katakana': + case 'x2017': + return 'JIS_C6220-1969-jp'; + + case 'csiso14jisc6220ro': + case 'iso646jp': + case 'isoir14': + case 'jisc62201969ro': + case 'jp': + return 'JIS_C6220-1969-ro'; + + case 'csiso42jisc62261978': + case 'isoir42': + case 'jisc62261978': + return 'JIS_C6226-1978'; + + case 'csiso87jisx208': + case 'isoir87': + case 'jisc62261983': + case 'jisx2081983': + case 'x208': + return 'JIS_C6226-1983'; + + case 'csiso91jisc62291984a': + case 'isoir91': + case 'jisc62291984a': + case 'jpocra': + return 'JIS_C6229-1984-a'; + + case 'csiso92jisc62991984b': + case 'iso646jpocrb': + case 'isoir92': + case 'jisc62291984b': + case 'jpocrb': + return 'JIS_C6229-1984-b'; + + case 'csiso93jis62291984badd': + case 'isoir93': + case 'jisc62291984badd': + case 'jpocrbadd': + return 'JIS_C6229-1984-b-add'; + + case 'csiso94jis62291984hand': + case 'isoir94': + case 'jisc62291984hand': + case 'jpocrhand': + return 'JIS_C6229-1984-hand'; + + case 'csiso95jis62291984handadd': + case 'isoir95': + case 'jisc62291984handadd': + case 'jpocrhandadd': + return 'JIS_C6229-1984-hand-add'; + + case 'csiso96jisc62291984kana': + case 'isoir96': + case 'jisc62291984kana': + return 'JIS_C6229-1984-kana'; + + case 'csjisencoding': + case 'jisencoding': + return 'JIS_Encoding'; + + case 'cshalfwidthkatakana': + case 'jisx201': + case 'x201': + return 'JIS_X0201'; + + case 'csiso159jisx2121990': + case 'isoir159': + case 'jisx2121990': + case 'x212': + return 'JIS_X0212-1990'; + + case 'csiso141jusib1002': + case 'iso646yu': + case 'isoir141': + case 'js': + case 'jusib1002': + case 'yu': + return 'JUS_I.B1.002'; + + case 'csiso147macedonian': + case 'isoir147': + case 'jusib1003mac': + case 'macedonian': + return 'JUS_I.B1.003-mac'; + + case 'csiso146serbian': + case 'isoir146': + case 'jusib1003serb': + case 'serbian': + return 'JUS_I.B1.003-serb'; + + case 'koi7switched': + return 'KOI7-switched'; + + case 'cskoi8r': + case 'koi8r': + return 'KOI8-R'; + + case 'koi8u': + return 'KOI8-U'; + + case 'csksc5636': + case 'iso646kr': + case 'ksc5636': + return 'KSC5636'; + + case 'cskz1048': + case 'kz1048': + case 'rk1048': + case 'strk10482002': + return 'KZ-1048'; + + case 'csiso19latingreek': + case 'isoir19': + case 'latingreek': + return 'latin-greek'; + + case 'csiso27latingreek1': + case 'isoir27': + case 'latingreek1': + return 'Latin-greek-1'; + + case 'csiso158lap': + case 'isoir158': + case 'lap': + case 'latinlap': + return 'latin-lap'; + + case 'csmacintosh': + case 'mac': + case 'macintosh': + return 'macintosh'; + + case 'csmicrosoftpublishing': + case 'microsoftpublishing': + return 'Microsoft-Publishing'; + + case 'csmnem': + case 'mnem': + return 'MNEM'; + + case 'csmnemonic': + case 'mnemonic': + return 'MNEMONIC'; + + case 'csiso86hungarian': + case 'hu': + case 'iso646hu': + case 'isoir86': + case 'msz77953': + return 'MSZ_7795.3'; + + case 'csnatsdano': + case 'isoir91': + case 'natsdano': + return 'NATS-DANO'; + + case 'csnatsdanoadd': + case 'isoir92': + case 'natsdanoadd': + return 'NATS-DANO-ADD'; + + case 'csnatssefi': + case 'isoir81': + case 'natssefi': + return 'NATS-SEFI'; + + case 'csnatssefiadd': + case 'isoir82': + case 'natssefiadd': + return 'NATS-SEFI-ADD'; + + case 'csiso151cuba': + case 'cuba': + case 'iso646cu': + case 'isoir151': + case 'ncnc1081': + return 'NC_NC00-10:81'; + + case 'csiso69french': + case 'fr': + case 'iso646fr': + case 'isoir69': + case 'nfz62010': + return 'NF_Z_62-010'; + + case 'csiso25french': + case 'iso646fr1': + case 'isoir25': + case 'nfz620101973': + return 'NF_Z_62-010_(1973)'; + + case 'csiso60danishnorwegian': + case 'csiso60norwegian1': + case 'iso646no': + case 'isoir60': + case 'no': + case 'ns45511': + return 'NS_4551-1'; + + case 'csiso61norwegian2': + case 'iso646no2': + case 'isoir61': + case 'no2': + case 'ns45512': + return 'NS_4551-2'; + + case 'osdebcdicdf3irv': + return 'OSD_EBCDIC_DF03_IRV'; + + case 'osdebcdicdf41': + return 'OSD_EBCDIC_DF04_1'; + + case 'osdebcdicdf415': + return 'OSD_EBCDIC_DF04_15'; + + case 'cspc8danishnorwegian': + case 'pc8danishnorwegian': + return 'PC8-Danish-Norwegian'; + + case 'cspc8turkish': + case 'pc8turkish': + return 'PC8-Turkish'; + + case 'csiso16portuguese': + case 'iso646pt': + case 'isoir16': + case 'pt': + return 'PT'; + + case 'csiso84portuguese2': + case 'iso646pt2': + case 'isoir84': + case 'pt2': + return 'PT2'; + + case 'cp154': + case 'csptcp154': + case 'cyrillicasian': + case 'pt154': + case 'ptcp154': + return 'PTCP154'; + + case 'scsu': + return 'SCSU'; + + case 'csiso10swedish': + case 'fi': + case 'iso646fi': + case 'iso646se': + case 'isoir10': + case 'se': + case 'sen850200b': + return 'SEN_850200_B'; + + case 'csiso11swedishfornames': + case 'iso646se2': + case 'isoir11': + case 'se2': + case 'sen850200c': + return 'SEN_850200_C'; + + case 'csiso102t617bit': + case 'isoir102': + case 't617bit': + return 'T.61-7bit'; + + case 'csiso103t618bit': + case 'isoir103': + case 't61': + case 't618bit': + return 'T.61-8bit'; + + case 'csiso128t101g2': + case 'isoir128': + case 't101g2': + return 'T.101-G2'; + + case 'cstscii': + case 'tscii': + return 'TSCII'; + + case 'csunicode11': + case 'unicode11': + return 'UNICODE-1-1'; + + case 'csunicode11utf7': + case 'unicode11utf7': + return 'UNICODE-1-1-UTF-7'; + + case 'csunknown8bit': + case 'unknown8bit': + return 'UNKNOWN-8BIT'; + + case 'ansix341968': + case 'ansix341986': + case 'ascii': + case 'cp367': + case 'csascii': + case 'ibm367': + case 'iso646irv1991': + case 'iso646us': + case 'isoir6': + case 'us': + case 'usascii': + return 'US-ASCII'; + + case 'csusdk': + case 'usdk': + return 'us-dk'; + + case 'utf7': + return 'UTF-7'; + + case 'utf8': + return 'UTF-8'; + + case 'utf16': + return 'UTF-16'; + + case 'utf16be': + return 'UTF-16BE'; + + case 'utf16le': + return 'UTF-16LE'; + + case 'utf32': + return 'UTF-32'; + + case 'utf32be': + return 'UTF-32BE'; + + case 'utf32le': + return 'UTF-32LE'; + + case 'csventurainternational': + case 'venturainternational': + return 'Ventura-International'; + + case 'csventuramath': + case 'venturamath': + return 'Ventura-Math'; + + case 'csventuraus': + case 'venturaus': + return 'Ventura-US'; + + case 'csiso70videotexsupp1': + case 'isoir70': + case 'videotexsuppl': + return 'videotex-suppl'; + + case 'csviqr': + case 'viqr': + return 'VIQR'; + + case 'csviscii': + case 'viscii': + return 'VISCII'; + + case 'csshiftjis': + case 'cswindows31j': + case 'mskanji': + case 'shiftjis': + case 'windows31j': + return 'Windows-31J'; + + case 'iso885911': + case 'tis620': + return 'windows-874'; + + case 'cseuckr': + case 'csksc56011987': + case 'euckr': + case 'isoir149': + case 'korean': + case 'ksc5601': + case 'ksc56011987': + case 'ksc56011989': + case 'windows949': + return 'windows-949'; + + case 'windows1250': + return 'windows-1250'; + + case 'windows1251': + return 'windows-1251'; + + case 'cp819': + case 'csisolatin1': + case 'ibm819': + case 'iso88591': + case 'iso885911987': + case 'isoir100': + case 'l1': + case 'latin1': + case 'windows1252': + return 'windows-1252'; + + case 'windows1253': + return 'windows-1253'; + + case 'csisolatin5': + case 'iso88599': + case 'iso885991989': + case 'isoir148': + case 'l5': + case 'latin5': + case 'windows1254': + return 'windows-1254'; + + case 'windows1255': + return 'windows-1255'; + + case 'windows1256': + return 'windows-1256'; + + case 'windows1257': + return 'windows-1257'; + + case 'windows1258': + return 'windows-1258'; + + default: + return $charset; + } + } + + /** + * @return string + */ + public static function get_curl_version() + { + if (is_array($curl = curl_version())) { + $curl = $curl['version']; + } else { + $curl = '0'; + } + return $curl; + } + + /** + * Strip HTML comments + * + * @deprecated since SimplePie 1.9.0. If you need it, you can copy the function to your codebase. But you should consider using `DOMDocument` for any DOM wrangling. + * @param string $data Data to strip comments from + * @return string Comment stripped string + */ + public static function strip_comments(string $data) + { + // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.9.'), \E_USER_DEPRECATED); + + $output = ''; + while (($start = strpos($data, '<!--')) !== false) { + $output .= substr($data, 0, $start); + if (($end = strpos($data, '-->', $start)) !== false) { + $data = substr_replace($data, '', 0, $end + 3); + } else { + $data = ''; + } + } + return $output . $data; + } + + /** + * @return int|false + */ + public static function parse_date(string $dt) + { + $parser = \SimplePie\Parse\Date::get(); + return $parser->parse($dt); + } + + /** + * Decode HTML entities + * + * @deprecated since SimplePie 1.3, use DOMDocument instead + * @param string $data Input data + * @return string Output data + */ + public static function entities_decode(string $data) + { + // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.3, use "DOMDocument" instead.'), \E_USER_DEPRECATED); + + $decoder = new \SimplePie_Decode_HTML_Entities($data); + return $decoder->parse(); + } + + /** + * Remove RFC822 comments + * + * @deprecated since SimplePie 1.9.0. If you need it, consider copying the function to your codebase. + * @param string $string Data to strip comments from + * @return string Comment stripped string + */ + public static function uncomment_rfc822(string $string) + { + // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.9.'), \E_USER_DEPRECATED); + + $position = 0; + $length = strlen($string); + $depth = 0; + + $output = ''; + + while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) { + $output .= substr($string, $position, $pos - $position); + $position = $pos + 1; + if ($string[$pos - 1] !== '\\') { + $depth++; + while ($depth && $position < $length) { + $position += strcspn($string, '()', $position); + if ($string[$position - 1] === '\\') { + $position++; + continue; + } elseif (isset($string[$position])) { + switch ($string[$position]) { + case '(': + $depth++; + break; + + case ')': + $depth--; + break; + } + $position++; + } else { + break; + } + } + } else { + $output .= '('; + } + } + $output .= substr($string, $position); + + return $output; + } + + /** + * @return string + */ + public static function parse_mime(string $mime) + { + if (($pos = strpos($mime, ';')) === false) { + return trim($mime); + } + + return trim(substr($mime, 0, $pos)); + } + + /** + * @param array<string, array<string, string>> $attribs + * @return int-mask-of<SimplePie::CONSTRUCT_*> + */ + public static function atom_03_construct_type(array $attribs) + { + if (isset($attribs['']['mode']) && strtolower(trim($attribs['']['mode'])) === 'base64') { + $mode = \SimplePie\SimplePie::CONSTRUCT_BASE64; + } else { + $mode = \SimplePie\SimplePie::CONSTRUCT_NONE; + } + if (isset($attribs['']['type'])) { + switch (strtolower(trim($attribs['']['type']))) { + case 'text': + case 'text/plain': + return \SimplePie\SimplePie::CONSTRUCT_TEXT | $mode; + + case 'html': + case 'text/html': + return \SimplePie\SimplePie::CONSTRUCT_HTML | $mode; + + case 'xhtml': + case 'application/xhtml+xml': + return \SimplePie\SimplePie::CONSTRUCT_XHTML | $mode; + + default: + return \SimplePie\SimplePie::CONSTRUCT_NONE | $mode; + } + } + + return \SimplePie\SimplePie::CONSTRUCT_TEXT | $mode; + } + + /** + * @param array<string, array<string, string>> $attribs + * @return int-mask-of<SimplePie::CONSTRUCT_*> + */ + public static function atom_10_construct_type(array $attribs) + { + if (isset($attribs['']['type'])) { + switch (strtolower(trim($attribs['']['type']))) { + case 'text': + return \SimplePie\SimplePie::CONSTRUCT_TEXT; + + case 'html': + return \SimplePie\SimplePie::CONSTRUCT_HTML; + + case 'xhtml': + return \SimplePie\SimplePie::CONSTRUCT_XHTML; + + default: + return \SimplePie\SimplePie::CONSTRUCT_NONE; + } + } + return \SimplePie\SimplePie::CONSTRUCT_TEXT; + } + + /** + * @param array<string, array<string, string>> $attribs + * @return int-mask-of<SimplePie::CONSTRUCT_*> + */ + public static function atom_10_content_construct_type(array $attribs) + { + if (isset($attribs['']['type'])) { + $type = strtolower(trim($attribs['']['type'])); + switch ($type) { + case 'text': + return \SimplePie\SimplePie::CONSTRUCT_TEXT; + + case 'html': + return \SimplePie\SimplePie::CONSTRUCT_HTML; + + case 'xhtml': + return \SimplePie\SimplePie::CONSTRUCT_XHTML; + } + if (in_array(substr($type, -4), ['+xml', '/xml']) || substr($type, 0, 5) === 'text/') { + return \SimplePie\SimplePie::CONSTRUCT_NONE; + } else { + return \SimplePie\SimplePie::CONSTRUCT_BASE64; + } + } + + return \SimplePie\SimplePie::CONSTRUCT_TEXT; + } + + /** + * @return bool + */ + public static function is_isegment_nz_nc(string $string) + { + return (bool) preg_match('/^([A-Za-z0-9\-._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!$&\'()*+,;=@]|(%[0-9ABCDEF]{2}))+$/u', $string); + } + + /** + * @return string[] + */ + public static function space_separated_tokens(string $string) + { + $space_characters = "\x20\x09\x0A\x0B\x0C\x0D"; + $string_length = strlen($string); + + $position = strspn($string, $space_characters); + $tokens = []; + + while ($position < $string_length) { + $len = strcspn($string, $space_characters, $position); + $tokens[] = substr($string, $position, $len); + $position += $len; + $position += strspn($string, $space_characters, $position); + } + + return $tokens; + } + + /** + * Converts a unicode codepoint to a UTF-8 character + * + * @static + * @param int $codepoint Unicode codepoint + * @return string|false UTF-8 character + */ + public static function codepoint_to_utf8(int $codepoint) + { + if ($codepoint < 0) { + return false; + } elseif ($codepoint <= 0x7f) { + return chr($codepoint); + } elseif ($codepoint <= 0x7ff) { + return chr(0xc0 | ($codepoint >> 6)) . chr(0x80 | ($codepoint & 0x3f)); + } elseif ($codepoint <= 0xffff) { + return chr(0xe0 | ($codepoint >> 12)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); + } elseif ($codepoint <= 0x10ffff) { + return chr(0xf0 | ($codepoint >> 18)) . chr(0x80 | (($codepoint >> 12) & 0x3f)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); + } + + // U+FFFD REPLACEMENT CHARACTER + return "\xEF\xBF\xBD"; + } + + /** + * Similar to parse_str() + * + * Returns an associative array of name/value pairs, where the value is an + * array of values that have used the same name + * + * @deprecated since SimplePie 1.9.0. If you need it, consider copying the function to your codebase. + * @static + * @param string $str The input string. + * @return array<string, array<string|null>> + */ + public static function parse_str(string $str) + { + // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.9.'), \E_USER_DEPRECATED); + + $return = []; + $str = explode('&', $str); + + foreach ($str as $section) { + if (strpos($section, '=') !== false) { + [$name, $value] = explode('=', $section, 2); + $return[urldecode($name)][] = urldecode($value); + } else { + $return[urldecode($section)][] = null; + } + } + + return $return; + } + + /** + * Detect XML encoding, as per XML 1.0 Appendix F.1 + * + * @todo Add support for EBCDIC + * @param string $data XML data + * @param \SimplePie\Registry $registry Class registry + * @return array<string> Possible encodings + */ + public static function xml_encoding(string $data, \SimplePie\Registry $registry) + { + // UTF-32 Big Endian BOM + if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") { + $encoding[] = 'UTF-32BE'; + } + // UTF-32 Little Endian BOM + elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") { + $encoding[] = 'UTF-32LE'; + } + // UTF-16 Big Endian BOM + elseif (substr($data, 0, 2) === "\xFE\xFF") { + $encoding[] = 'UTF-16BE'; + } + // UTF-16 Little Endian BOM + elseif (substr($data, 0, 2) === "\xFF\xFE") { + $encoding[] = 'UTF-16LE'; + } + // UTF-8 BOM + elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") { + $encoding[] = 'UTF-8'; + } + // UTF-32 Big Endian Without BOM + elseif (substr($data, 0, 20) === "\x00\x00\x00\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C") { + if ($pos = strpos($data, "\x00\x00\x00\x3F\x00\x00\x00\x3E")) { + $parser = $registry->create(Parser::class, [Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32BE', 'UTF-8')]); + if ($parser->parse()) { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-32BE'; + } + // UTF-32 Little Endian Without BOM + elseif (substr($data, 0, 20) === "\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C\x00\x00\x00") { + if ($pos = strpos($data, "\x3F\x00\x00\x00\x3E\x00\x00\x00")) { + $parser = $registry->create(Parser::class, [Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32LE', 'UTF-8')]); + if ($parser->parse()) { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-32LE'; + } + // UTF-16 Big Endian Without BOM + elseif (substr($data, 0, 10) === "\x00\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C") { + if ($pos = strpos($data, "\x00\x3F\x00\x3E")) { + $parser = $registry->create(Parser::class, [Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16BE', 'UTF-8')]); + if ($parser->parse()) { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-16BE'; + } + // UTF-16 Little Endian Without BOM + elseif (substr($data, 0, 10) === "\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C\x00") { + if ($pos = strpos($data, "\x3F\x00\x3E\x00")) { + $parser = $registry->create(Parser::class, [Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16LE', 'UTF-8')]); + if ($parser->parse()) { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-16LE'; + } + // US-ASCII (or superset) + elseif (substr($data, 0, 5) === "\x3C\x3F\x78\x6D\x6C") { + if ($pos = strpos($data, "\x3F\x3E")) { + $parser = $registry->create(Parser::class, [substr($data, 5, $pos - 5)]); + if ($parser->parse()) { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-8'; + } + // Fallback to UTF-8 + else { + $encoding[] = 'UTF-8'; + } + return $encoding; + } + + /** + * @return void + */ + public static function output_javascript() + { + if (function_exists('ob_gzhandler')) { + ob_start('ob_gzhandler'); + } + header('Content-type: text/javascript; charset: UTF-8'); + header('Cache-Control: must-revalidate'); + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days + + $body = <<<JS +function embed_quicktime(type, bgcolor, width, height, link, placeholder, loop) { + if (placeholder != '') { + document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" href="'+link+'" src="'+placeholder+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="false" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>'); + } + else { + document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" src="'+link+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="true" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>'); + } +} + +function embed_flash(bgcolor, width, height, link, loop, type) { + document.writeln('<embed src="'+link+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="'+type+'" quality="high" width="'+width+'" height="'+height+'" bgcolor="'+bgcolor+'" loop="'+loop+'"></embed>'); +} + +function embed_flv(width, height, link, placeholder, loop, player) { + document.writeln('<embed src="'+player+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="'+width+'" height="'+height+'" wmode="transparent" flashvars="file='+link+'&autostart=false&repeat='+loop+'&showdigits=true&showfsbutton=false"></embed>'); +} + +function embed_wmedia(width, height, link) { + document.writeln('<embed type="application/x-mplayer2" src="'+link+'" autosize="1" width="'+width+'" height="'+height+'" showcontrols="1" showstatusbar="0" showdisplay="0" autostart="0"></embed>'); +} +JS; + echo $body; + } + + /** + * Get the SimplePie build timestamp + * + * Uses the git index if it exists, otherwise uses the modification time + * of the newest file. + * + * @return int + */ + public static function get_build() + { + if (self::$SIMPLEPIE_BUILD !== null) { + return self::$SIMPLEPIE_BUILD; + } + + $root = dirname(__FILE__, 2); + if (file_exists($root . '/.git/index')) { + self::$SIMPLEPIE_BUILD = (int) filemtime($root . '/.git/index'); + + return self::$SIMPLEPIE_BUILD; + } elseif (file_exists($root . '/src')) { + $time = 0; + foreach (glob($root . '/src/*.php') ?: [] as $file) { + if (($mtime = filemtime($file)) > $time) { + $time = $mtime; + } + } + + self::$SIMPLEPIE_BUILD = $time; + + return self::$SIMPLEPIE_BUILD; + } + + self::$SIMPLEPIE_BUILD = (int) filemtime(__FILE__); + + return self::$SIMPLEPIE_BUILD; + } + + /** + * Get the default user agent string + * + * @return string + */ + public static function get_default_useragent() + { + return \SimplePie\SimplePie::NAME . '/' . \SimplePie\SimplePie::VERSION . ' (Feed Parser; ' . \SimplePie\SimplePie::URL . '; Allow like Gecko) Build/' . static::get_build(); + } + + /** + * Format debugging information + * + * @return string + */ + public static function debug(SimplePie &$sp) + { + $info = 'SimplePie ' . \SimplePie\SimplePie::VERSION . ' Build ' . static::get_build() . "\n"; + $info .= 'PHP ' . PHP_VERSION . "\n"; + if ($sp->error() !== null) { + // TODO: Remove cast with multifeeds. + $info .= 'Error occurred: ' . implode(', ', (array) $sp->error()) . "\n"; + } else { + $info .= "No error found.\n"; + } + $info .= "Extensions:\n"; + $extensions = ['pcre', 'curl', 'zlib', 'mbstring', 'iconv', 'xmlreader', 'xml']; + foreach ($extensions as $ext) { + if (extension_loaded($ext)) { + $info .= " $ext loaded\n"; + switch ($ext) { + case 'pcre': + $info .= ' Version ' . PCRE_VERSION . "\n"; + break; + case 'curl': + $version = (array) curl_version(); + $info .= ' Version ' . $version['version'] . "\n"; + break; + case 'iconv': + $info .= ' Version ' . ICONV_VERSION . "\n"; + break; + case 'xml': + $info .= ' Version ' . LIBXML_DOTTED_VERSION . "\n"; + break; + } + } else { + $info .= " $ext not loaded\n"; + } + } + return $info; + } + + /** + * @return bool + */ + public static function silence_errors(int $num, string $str) + { + // No-op + return true; + } + + /** + * Sanitize a URL by removing HTTP credentials. + * @param string $url the URL to sanitize. + * @return string the same URL without HTTP credentials. + */ + public static function url_remove_credentials(string $url) + { + // Cast for PHPStan: I do not think this can fail. + // The regex is valid and there should be no backtracking. + // https://github.com/phpstan/phpstan/issues/11547 + return (string) preg_replace('#^(https?://)[^/:@]+:[^/:@]+@#i', '$1', $url); + } +} + +class_alias('SimplePie\Misc', 'SimplePie_Misc', false); + +} + +namespace SimplePie\Net { + +/** + * Class to validate and to work with IPv6 addresses. + * + * @copyright 2003-2005 The PHP Group + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/package/Net_IPv6 + * @author Alexander Merz <alexander.merz@web.de> + * @author elfrink at introweb dot nl + * @author Josh Peck <jmp at joshpeck dot org> + * @author Sam Sneddon <geoffers@gmail.com> + */ +class IPv6 +{ + /** + * Uncompresses an IPv6 address + * + * RFC 4291 allows you to compress consecutive zero pieces in an address to + * '::'. This method expects a valid IPv6 address and expands the '::' to + * the required number of zero pieces. + * + * Example: FF01::101 -> FF01:0:0:0:0:0:0:101 + * ::1 -> 0:0:0:0:0:0:0:1 + * + * @author Alexander Merz <alexander.merz@web.de> + * @author elfrink at introweb dot nl + * @author Josh Peck <jmp at joshpeck dot org> + * @copyright 2003-2005 The PHP Group + * @license http://www.opensource.org/licenses/bsd-license.php + * @param string $ip An IPv6 address + * @return string The uncompressed IPv6 address + */ + public static function uncompress(string $ip) + { + $c1 = -1; + $c2 = -1; + if (substr_count($ip, '::') === 1) { + [$ip1, $ip2] = explode('::', $ip); + if ($ip1 === '') { + $c1 = -1; + } else { + $c1 = substr_count($ip1, ':'); + } + if ($ip2 === '') { + $c2 = -1; + } else { + $c2 = substr_count($ip2, ':'); + } + if (strpos($ip2, '.') !== false) { + $c2++; + } + // :: + if ($c1 === -1 && $c2 === -1) { + $ip = '0:0:0:0:0:0:0:0'; + } + // ::xxx + elseif ($c1 === -1) { + $fill = str_repeat('0:', 7 - $c2); + $ip = str_replace('::', $fill, $ip); + } + // xxx:: + elseif ($c2 === -1) { + $fill = str_repeat(':0', 7 - $c1); + $ip = str_replace('::', $fill, $ip); + } + // xxx::xxx + else { + $fill = ':' . str_repeat('0:', 6 - $c2 - $c1); + $ip = str_replace('::', $fill, $ip); + } + } + return $ip; + } + + /** + * Compresses an IPv6 address + * + * RFC 4291 allows you to compress consecutive zero pieces in an address to + * '::'. This method expects a valid IPv6 address and compresses consecutive + * zero pieces to '::'. + * + * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 + * 0:0:0:0:0:0:0:1 -> ::1 + * + * @see uncompress() + * @param string $ip An IPv6 address + * @return string The compressed IPv6 address + */ + public static function compress(string $ip) + { + // Prepare the IP to be compressed + $ip = self::uncompress($ip); + $ip_parts = self::split_v6_v4($ip); + + // Replace all leading zeros + $ip_parts[0] = (string) preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]); + + // Find bunches of zeros + if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) { + $max = 0; + $pos = null; + foreach ($matches[0] as $match) { + if (strlen($match[0]) > $max) { + $max = strlen($match[0]); + $pos = $match[1]; + } + } + + assert($pos !== null, 'For PHPStan: Since the regex matched, there is at least one match. And because the pattern is non-empty, the loop will always end with $pos ≥ 1.'); + $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max); + } + + if ($ip_parts[1] !== '') { + return implode(':', $ip_parts); + } + + return $ip_parts[0]; + } + + /** + * Splits an IPv6 address into the IPv6 and IPv4 representation parts + * + * RFC 4291 allows you to represent the last two parts of an IPv6 address + * using the standard IPv4 representation + * + * Example: 0:0:0:0:0:0:13.1.68.3 + * 0:0:0:0:0:FFFF:129.144.52.38 + * + * @param string $ip An IPv6 address + * @return array{string, string} [0] contains the IPv6 represented part, and [1] the IPv4 represented part + */ + private static function split_v6_v4(string $ip): array + { + if (strpos($ip, '.') !== false) { + $pos = strrpos($ip, ':'); + assert($pos !== false, 'For PHPStan: IPv6 address must contain colon, since split_v6_v4 is only ever called after uncompress.'); + $ipv6_part = substr($ip, 0, $pos); + $ipv4_part = substr($ip, $pos + 1); + return [$ipv6_part, $ipv4_part]; + } + + return [$ip, '']; + } + + /** + * Checks an IPv6 address + * + * Checks if the given IP is a valid IPv6 address + * + * @param string $ip An IPv6 address + * @return bool true if $ip is a valid IPv6 address + */ + public static function check_ipv6(string $ip) + { + $ip = self::uncompress($ip); + [$ipv6, $ipv4] = self::split_v6_v4($ip); + $ipv6 = explode(':', $ipv6); + $ipv4 = explode('.', $ipv4); + if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) { + foreach ($ipv6 as $ipv6_part) { + // The section can't be empty + if ($ipv6_part === '') { + return false; + } + + // Nor can it be over four characters + if (strlen($ipv6_part) > 4) { + return false; + } + + // Remove leading zeros (this is safe because of the above) + $ipv6_part = ltrim($ipv6_part, '0'); + if ($ipv6_part === '') { + $ipv6_part = '0'; + } + + // Check the value is valid + $value = hexdec($ipv6_part); + if ($value < 0 || $value > 0xFFFF) { + return false; + } + assert(is_int($value), 'For PHPStan: $value is only float when $ipv6_part > PHP_INT_MAX'); + if (dechex($value) !== strtolower($ipv6_part)) { + return false; + } + } + if (count($ipv4) === 4) { + foreach ($ipv4 as $ipv4_part) { + $value = (int) $ipv4_part; + if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) { + return false; + } + } + } + return true; + } + + return false; + } + + /** + * Checks if the given IP is a valid IPv6 address + * + * @codeCoverageIgnore + * @deprecated Use {@see IPv6::check_ipv6()} instead + * @see check_ipv6 + * @param string $ip An IPv6 address + * @return bool true if $ip is a valid IPv6 address + */ + public static function checkIPv6(string $ip) + { + return self::check_ipv6($ip); + } +} + +class_alias('SimplePie\Net\IPv6', 'SimplePie_Net_IPv6'); + +} + +namespace SimplePie\Parse { + +/** + * Date Parser + */ +class Date +{ + /** + * Input data + * + * @access protected + * @var string + */ + public $date; + + /** + * List of days, calendar day name => ordinal day number in the week + * + * @access protected + * @var array<string, int<1,7>> + */ + public $day = [ + // English + 'mon' => 1, + 'monday' => 1, + 'tue' => 2, + 'tuesday' => 2, + 'wed' => 3, + 'wednesday' => 3, + 'thu' => 4, + 'thursday' => 4, + 'fri' => 5, + 'friday' => 5, + 'sat' => 6, + 'saturday' => 6, + 'sun' => 7, + 'sunday' => 7, + // Dutch + 'maandag' => 1, + 'dinsdag' => 2, + 'woensdag' => 3, + 'donderdag' => 4, + 'vrijdag' => 5, + 'zaterdag' => 6, + 'zondag' => 7, + // French + 'lundi' => 1, + 'mardi' => 2, + 'mercredi' => 3, + 'jeudi' => 4, + 'vendredi' => 5, + 'samedi' => 6, + 'dimanche' => 7, + // German + 'montag' => 1, + 'mo' => 1, + 'dienstag' => 2, + 'di' => 2, + 'mittwoch' => 3, + 'mi' => 3, + 'donnerstag' => 4, + 'do' => 4, + 'freitag' => 5, + 'fr' => 5, + 'samstag' => 6, + 'sa' => 6, + 'sonnabend' => 6, + // AFAIK no short form for sonnabend + 'so' => 7, + 'sonntag' => 7, + // Italian + 'lunedì' => 1, + 'martedì' => 2, + 'mercoledì' => 3, + 'giovedì' => 4, + 'venerdì' => 5, + 'sabato' => 6, + 'domenica' => 7, + // Spanish + 'lunes' => 1, + 'martes' => 2, + 'miércoles' => 3, + 'jueves' => 4, + 'viernes' => 5, + 'sábado' => 6, + 'domingo' => 7, + // Finnish + 'maanantai' => 1, + 'tiistai' => 2, + 'keskiviikko' => 3, + 'torstai' => 4, + 'perjantai' => 5, + 'lauantai' => 6, + 'sunnuntai' => 7, + // Hungarian + 'hétfő' => 1, + 'kedd' => 2, + 'szerda' => 3, + 'csütörtok' => 4, + 'péntek' => 5, + 'szombat' => 6, + 'vasárnap' => 7, + // Greek + 'Δευ' => 1, + 'Τρι' => 2, + 'Τετ' => 3, + 'Πεμ' => 4, + 'Παρ' => 5, + 'Σαβ' => 6, + 'Κυρ' => 7, + // Russian + 'Пн.' => 1, + 'Вт.' => 2, + 'Ср.' => 3, + 'Чт.' => 4, + 'Пт.' => 5, + 'Сб.' => 6, + 'Вс.' => 7, + ]; + + /** + * List of months, calendar month name => calendar month number + * + * @access protected + * @var array<string, int<1,12>> + */ + public $month = [ + // English + 'jan' => 1, + 'january' => 1, + 'feb' => 2, + 'february' => 2, + 'mar' => 3, + 'march' => 3, + 'apr' => 4, + 'april' => 4, + 'may' => 5, + // No long form of May + 'jun' => 6, + 'june' => 6, + 'jul' => 7, + 'july' => 7, + 'aug' => 8, + 'august' => 8, + 'sep' => 9, + 'september' => 9, + 'oct' => 10, + 'october' => 10, + 'nov' => 11, + 'november' => 11, + 'dec' => 12, + 'december' => 12, + // Dutch + 'januari' => 1, + 'februari' => 2, + 'maart' => 3, + // 'april' => 4, + 'mei' => 5, + 'juni' => 6, + 'juli' => 7, + 'augustus' => 8, + // 'september' => 9, + 'oktober' => 10, + // 'november' => 11, + // 'december' => 12, + // French + 'janvier' => 1, + 'février' => 2, + 'mars' => 3, + 'avril' => 4, + 'mai' => 5, + 'juin' => 6, + 'juillet' => 7, + 'août' => 8, + 'septembre' => 9, + 'octobre' => 10, + 'novembre' => 11, + 'décembre' => 12, + // German + 'januar' => 1, + // 'jan' => 1, + 'februar' => 2, + // 'feb' => 2, + 'märz' => 3, + 'mär' => 3, + // 'april' => 4, + // 'apr' => 4, + // 'mai' => 5, // no short form for may + // 'juni' => 6, + // 'jun' => 6, + // 'juli' => 7, + // 'jul' => 7, + // 'august' => 8, + // 'aug' => 8, + // 'september' => 9, + // 'sep' => 9, + // 'oktober' => 10, + 'okt' => 10, + // 'november' => 11, + // 'nov' => 11, + 'dezember' => 12, + 'dez' => 12, + // Italian + 'gennaio' => 1, + 'febbraio' => 2, + 'marzo' => 3, + 'aprile' => 4, + 'maggio' => 5, + 'giugno' => 6, + 'luglio' => 7, + 'agosto' => 8, + 'settembre' => 9, + 'ottobre' => 10, + // 'novembre' => 11, + 'dicembre' => 12, + // Spanish + 'enero' => 1, + 'febrero' => 2, + // 'marzo' => 3, + 'abril' => 4, + 'mayo' => 5, + 'junio' => 6, + 'julio' => 7, + // 'agosto' => 8, + 'septiembre' => 9, + 'setiembre' => 9, + 'octubre' => 10, + 'noviembre' => 11, + 'diciembre' => 12, + // Finnish + 'tammikuu' => 1, + 'helmikuu' => 2, + 'maaliskuu' => 3, + 'huhtikuu' => 4, + 'toukokuu' => 5, + 'kesäkuu' => 6, + 'heinäkuu' => 7, + 'elokuu' => 8, + 'suuskuu' => 9, + 'lokakuu' => 10, + 'marras' => 11, + 'joulukuu' => 12, + // Hungarian + 'január' => 1, + 'február' => 2, + 'március' => 3, + 'április' => 4, + 'május' => 5, + 'június' => 6, + 'július' => 7, + 'augusztus' => 8, + 'szeptember' => 9, + 'október' => 10, + // 'november' => 11, + // 'december' => 12, + // Greek + 'Ιαν' => 1, + 'Φεβ' => 2, + 'Μάώ' => 3, + 'Μαώ' => 3, + 'Απρ' => 4, + 'Μάι' => 5, + 'Μαϊ' => 5, + 'Μαι' => 5, + 'Ιούν' => 6, + 'Ιον' => 6, + 'Ιούλ' => 7, + 'Ιολ' => 7, + 'Αύγ' => 8, + 'Αυγ' => 8, + 'Σεπ' => 9, + 'Οκτ' => 10, + 'Νοέ' => 11, + 'Δεκ' => 12, + // Russian + 'Янв' => 1, + 'января' => 1, + 'Фев' => 2, + 'февраля' => 2, + 'Мар' => 3, + 'марта' => 3, + 'Апр' => 4, + 'апреля' => 4, + 'Май' => 5, + 'мая' => 5, + 'Июн' => 6, + 'июня' => 6, + 'Июл' => 7, + 'июля' => 7, + 'Авг' => 8, + 'августа' => 8, + 'Сен' => 9, + 'сентября' => 9, + 'Окт' => 10, + 'октября' => 10, + 'Ноя' => 11, + 'ноября' => 11, + 'Дек' => 12, + 'декабря' => 12, + + ]; + + /** + * List of timezones, abbreviation => offset from UTC + * + * @access protected + * @var array<string, int> + */ + public $timezone = [ + 'ACDT' => 37800, + 'ACIT' => 28800, + 'ACST' => 34200, + 'ACT' => -18000, + 'ACWDT' => 35100, + 'ACWST' => 31500, + 'AEDT' => 39600, + 'AEST' => 36000, + 'AFT' => 16200, + 'AKDT' => -28800, + 'AKST' => -32400, + 'AMDT' => 18000, + 'AMT' => -14400, + 'ANAST' => 46800, + 'ANAT' => 43200, + 'ART' => -10800, + 'AZOST' => -3600, + 'AZST' => 18000, + 'AZT' => 14400, + 'BIOT' => 21600, + 'BIT' => -43200, + 'BOT' => -14400, + 'BRST' => -7200, + 'BRT' => -10800, + 'BST' => 3600, + 'BTT' => 21600, + 'CAST' => 18000, + 'CAT' => 7200, + 'CCT' => 23400, + 'CDT' => -18000, + 'CEDT' => 7200, + 'CEST' => 7200, + 'CET' => 3600, + 'CGST' => -7200, + 'CGT' => -10800, + 'CHADT' => 49500, + 'CHAST' => 45900, + 'CIST' => -28800, + 'CKT' => -36000, + 'CLDT' => -10800, + 'CLST' => -14400, + 'COT' => -18000, + 'CST' => -21600, + 'CVT' => -3600, + 'CXT' => 25200, + 'DAVT' => 25200, + 'DTAT' => 36000, + 'EADT' => -18000, + 'EAST' => -21600, + 'EAT' => 10800, + 'ECT' => -18000, + 'EDT' => -14400, + 'EEST' => 10800, + 'EET' => 7200, + 'EGT' => -3600, + 'EKST' => 21600, + 'EST' => -18000, + 'FJT' => 43200, + 'FKDT' => -10800, + 'FKST' => -14400, + 'FNT' => -7200, + 'GALT' => -21600, + 'GEDT' => 14400, + 'GEST' => 10800, + 'GFT' => -10800, + 'GILT' => 43200, + 'GIT' => -32400, + 'GST' => 14400, + // 'GST' => -7200, + 'GYT' => -14400, + 'HAA' => -10800, + 'HAC' => -18000, + 'HADT' => -32400, + 'HAE' => -14400, + 'HAP' => -25200, + 'HAR' => -21600, + 'HAST' => -36000, + 'HAT' => -9000, + 'HAY' => -28800, + 'HKST' => 28800, + 'HMT' => 18000, + 'HNA' => -14400, + 'HNC' => -21600, + 'HNE' => -18000, + 'HNP' => -28800, + 'HNR' => -25200, + 'HNT' => -12600, + 'HNY' => -32400, + 'IRDT' => 16200, + 'IRKST' => 32400, + 'IRKT' => 28800, + 'IRST' => 12600, + 'JFDT' => -10800, + 'JFST' => -14400, + 'JST' => 32400, + 'KGST' => 21600, + 'KGT' => 18000, + 'KOST' => 39600, + 'KOVST' => 28800, + 'KOVT' => 25200, + 'KRAST' => 28800, + 'KRAT' => 25200, + 'KST' => 32400, + 'LHDT' => 39600, + 'LHST' => 37800, + 'LINT' => 50400, + 'LKT' => 21600, + 'MAGST' => 43200, + 'MAGT' => 39600, + 'MAWT' => 21600, + 'MDT' => -21600, + 'MESZ' => 7200, + 'MEZ' => 3600, + 'MHT' => 43200, + 'MIT' => -34200, + 'MNST' => 32400, + 'MSDT' => 14400, + 'MSST' => 10800, + 'MST' => -25200, + 'MUT' => 14400, + 'MVT' => 18000, + 'MYT' => 28800, + 'NCT' => 39600, + 'NDT' => -9000, + 'NFT' => 41400, + 'NMIT' => 36000, + 'NOVST' => 25200, + 'NOVT' => 21600, + 'NPT' => 20700, + 'NRT' => 43200, + 'NST' => -12600, + 'NUT' => -39600, + 'NZDT' => 46800, + 'NZST' => 43200, + 'OMSST' => 25200, + 'OMST' => 21600, + 'PDT' => -25200, + 'PET' => -18000, + 'PETST' => 46800, + 'PETT' => 43200, + 'PGT' => 36000, + 'PHOT' => 46800, + 'PHT' => 28800, + 'PKT' => 18000, + 'PMDT' => -7200, + 'PMST' => -10800, + 'PONT' => 39600, + 'PST' => -28800, + 'PWT' => 32400, + 'PYST' => -10800, + 'PYT' => -14400, + 'RET' => 14400, + 'ROTT' => -10800, + 'SAMST' => 18000, + 'SAMT' => 14400, + 'SAST' => 7200, + 'SBT' => 39600, + 'SCDT' => 46800, + 'SCST' => 43200, + 'SCT' => 14400, + 'SEST' => 3600, + 'SGT' => 28800, + 'SIT' => 28800, + 'SRT' => -10800, + 'SST' => -39600, + 'SYST' => 10800, + 'SYT' => 7200, + 'TFT' => 18000, + 'THAT' => -36000, + 'TJT' => 18000, + 'TKT' => -36000, + 'TMT' => 18000, + 'TOT' => 46800, + 'TPT' => 32400, + 'TRUT' => 36000, + 'TVT' => 43200, + 'TWT' => 28800, + 'UYST' => -7200, + 'UYT' => -10800, + 'UZT' => 18000, + 'VET' => -14400, + 'VLAST' => 39600, + 'VLAT' => 36000, + 'VOST' => 21600, + 'VUT' => 39600, + 'WAST' => 7200, + 'WAT' => 3600, + 'WDT' => 32400, + 'WEST' => 3600, + 'WFT' => 43200, + 'WIB' => 25200, + 'WIT' => 32400, + 'WITA' => 28800, + 'WKST' => 18000, + 'WST' => 28800, + 'YAKST' => 36000, + 'YAKT' => 32400, + 'YAPT' => 36000, + 'YEKST' => 21600, + 'YEKT' => 18000, + ]; + + /** + * Cached PCRE for Date::$day + * + * @access protected + * @var string + */ + public $day_pcre; + + /** + * Cached PCRE for Date::$month + * + * @access protected + * @var string + */ + public $month_pcre; + + /** + * Array of user-added callback methods + * + * @access private + * @var array<string> + */ + public $built_in = []; + + /** + * Array of user-added callback methods + * + * @access private + * @var array<callable(string): (int|false)> + */ + public $user = []; + + /** + * Create new Date object, and set self::day_pcre, + * self::month_pcre, and self::built_in + * + * @access private + */ + public function __construct() + { + $this->day_pcre = '(' . implode('|', array_keys($this->day)) . ')'; + $this->month_pcre = '(' . implode('|', array_keys($this->month)) . ')'; + + static $cache; + if (!isset($cache[get_class($this)])) { + $all_methods = get_class_methods($this); + + foreach ($all_methods as $method) { + if (strtolower(substr($method, 0, 5)) === 'date_') { + $cache[get_class($this)][] = $method; + } + } + } + + foreach ($cache[get_class($this)] as $method) { + $this->built_in[] = $method; + } + } + + /** + * Get the object + * + * @access public + * @return Date + */ + public static function get() + { + static $object; + if (!$object) { + $object = new Date(); + } + return $object; + } + + /** + * Parse a date + * + * @final + * @access public + * @param string $date Date to parse + * @return int|false Timestamp corresponding to date string, or false on failure + */ + public function parse(string $date) + { + foreach ($this->user as $method) { + if (($returned = call_user_func($method, $date)) !== false) { + return (int) $returned; + } + } + + foreach ($this->built_in as $method) { + // TODO: we should really check this in constructor but that would require private properties. + /** @var callable(string): (int|false) */ + $callable = [$this, $method]; + if (($returned = call_user_func($callable, $date)) !== false) { + return $returned; + } + } + + return false; + } + + /** + * Add a callback method to parse a date + * + * @final + * @access public + * @param callable $callback + * @return void + */ + public function add_callback(callable $callback) + { + $this->user[] = $callback; + } + + /** + * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as + * well as allowing any of upper or lower case "T", horizontal tabs, or + * spaces to be used as the time separator (including more than one)) + * + * @access protected + * @param string $date + * @return int|false Timestamp + */ + public function date_w3cdtf(string $date) + { + $pcre = <<<'PCRE' + / + ^ + (?P<year>[0-9]{4}) + (?: + -? + (?P<month>[0-9]{2}) + (?: + -? + (?P<day>[0-9]{2}) + (?: + [Tt\x09\x20]+ + (?P<hour>[0-9]{2}) + (?: + :? + (?P<minute>[0-9]{2}) + (?: + :? + (?P<second>[0-9]{2}) + (?: + . + (?P<second_fraction>[0-9]*) + )? + )? + )? + (?: + (?P<zulu>Z) + | (?P<tz_sign>[+\-]) + (?P<tz_hour>[0-9]{1,2}) + :? + (?P<tz_minute>[0-9]{1,2}) + ) + )? + )? + )? + $ + /x +PCRE; + if (preg_match($pcre, $date, $match)) { + // Fill in empty matches and convert to proper types. + $year = (int) $match['year']; + $month = isset($match['month']) ? (int) $match['month'] : 1; + $day = isset($match['day']) ? (int) $match['day'] : 1; + $hour = isset($match['hour']) ? (int) $match['hour'] : 0; + $minute = isset($match['minute']) ? (int) $match['minute'] : 0; + $second = isset($match['second']) ? (int) $match['second'] : 0; + $second_fraction = isset($match['second_fraction']) ? ((int) $match['second_fraction']) / (10 ** strlen($match['second_fraction'])) : 0; + $tz_sign = ($match['tz_sign'] ?? '') === '-' ? -1 : 1; + $tz_hour = isset($match['tz_hour']) ? (int) $match['tz_hour'] : 0; + $tz_minute = isset($match['tz_minute']) ? (int) $match['tz_minute'] : 0; + + // Numeric timezone + $timezone = $tz_hour * 3600; + $timezone += $tz_minute * 60; + $timezone *= $tz_sign; + + // Convert the number of seconds to an integer, taking decimals into account + $second = (int) round($second + $second_fraction); + + return gmmktime($hour, $minute, $second, $month, $day, $year) - $timezone; + } + + return false; + } + + /** + * Remove RFC822 comments + * + * @access protected + * @param string $string Data to strip comments from + * @return string Comment stripped string + */ + public function remove_rfc2822_comments(string $string) + { + $position = 0; + $length = strlen($string); + $depth = 0; + + $output = ''; + + while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) { + $output .= substr($string, $position, $pos - $position); + $position = $pos + 1; + if ($pos === 0 || $string[$pos - 1] !== '\\') { + $depth++; + while ($depth && $position < $length) { + $position += strcspn($string, '()', $position); + if ($string[$position - 1] === '\\') { + $position++; + continue; + } elseif (isset($string[$position])) { + switch ($string[$position]) { + case '(': + $depth++; + break; + + case ')': + $depth--; + break; + } + $position++; + } else { + break; + } + } + } else { + $output .= '('; + } + } + $output .= substr($string, $position); + + return $output; + } + + /** + * Parse RFC2822's date format + * + * @access protected + * @param string $date + * @return int|false Timestamp + */ + public function date_rfc2822(string $date) + { + static $pcre; + if (!$pcre) { + $wsp = '[\x09\x20]'; + $fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)'; + $optional_fws = $fws . '?'; + $day_name = $this->day_pcre; + $month = $this->month_pcre; + $day = '([0-9]{1,2})'; + $hour = $minute = $second = '([0-9]{2})'; + $year = '([0-9]{2,4})'; + $num_zone = '([+\-])([0-9]{2})([0-9]{2})'; + $character_zone = '([A-Z]{1,5})'; + $zone = '(?:' . $num_zone . '|' . $character_zone . ')'; + $pcre = '/(?:' . $optional_fws . $day_name . $optional_fws . ',)?' . $optional_fws . $day . $fws . $month . $fws . $year . $fws . $hour . $optional_fws . ':' . $optional_fws . $minute . '(?:' . $optional_fws . ':' . $optional_fws . $second . ')?' . $fws . $zone . '/i'; + } + if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match)) { + /* + Capturing subpatterns: + 1: Day name + 2: Day + 3: Month + 4: Year + 5: Hour + 6: Minute + 7: Second + 8: Timezone ± + 9: Timezone hours + 10: Timezone minutes + 11: Alphabetic timezone + */ + + $day = (int) $match[2]; + // Find the month number + $month = $this->month[strtolower($match[3])]; + $year = (int) $match[4]; + $hour = (int) $match[5]; + $minute = (int) $match[6]; + // Second is optional, if it is empty set it to zero + $second = (int) $match[7]; + + $tz_sign = $match[8]; + $tz_hour = (int) $match[9]; + $tz_minute = (int) $match[10]; + $tz_code = isset($match[11]) ? strtoupper($match[11]) : ''; + + // Numeric timezone + if ($tz_sign !== '') { + $timezone = $tz_hour * 3600; + $timezone += $tz_minute * 60; + if ($tz_sign === '-') { + $timezone = 0 - $timezone; + } + } + // Character timezone + elseif (isset($this->timezone[$tz_code])) { + $timezone = $this->timezone[$tz_code]; + } + // Assume everything else to be -0000 + else { + $timezone = 0; + } + + // Deal with 2/3 digit years + if ($year < 50) { + $year += 2000; + } elseif ($year < 1000) { + $year += 1900; + } + + return gmmktime($hour, $minute, $second, $month, $day, $year) - $timezone; + } + + return false; + } + + /** + * Parse RFC850's date format + * + * @access protected + * @param string $date + * @return int|false Timestamp + */ + public function date_rfc850(string $date) + { + static $pcre; + if (!$pcre) { + $space = '[\x09\x20]+'; + $day_name = $this->day_pcre; + $month = $this->month_pcre; + $day = '([0-9]{1,2})'; + $year = $hour = $minute = $second = '([0-9]{2})'; + $zone = '([A-Z]{1,5})'; + $pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i'; + } + if (preg_match($pcre, $date, $match)) { + /* + Capturing subpatterns: + 1: Day name + 2: Day + 3: Month + 4: Year + 5: Hour + 6: Minute + 7: Second + 8: Timezone + */ + + $day = (int) $match[2]; + // Month + $month = $this->month[strtolower($match[3])]; + $year = (int) $match[4]; + $hour = (int) $match[5]; + $minute = (int) $match[6]; + // Second is optional, if it is empty set it to zero + $second = (int) $match[7]; + + $tz_code = strtoupper($match[8]); + + // Character timezone + if (isset($this->timezone[$tz_code])) { + $timezone = $this->timezone[$tz_code]; + } + // Assume everything else to be -0000 + else { + $timezone = 0; + } + + // Deal with 2 digit year + if ($year < 50) { + $year += 2000; + } else { + $year += 1900; + } + + return gmmktime($hour, $minute, $second, $month, $day, $year) - $timezone; + } + + return false; + } + + /** + * Parse C99's asctime()'s date format + * + * @access protected + * @param string $date + * @return int|false Timestamp + */ + public function date_asctime(string $date) + { + static $pcre; + if (!$pcre) { + $space = '[\x09\x20]+'; + $wday_name = $this->day_pcre; + $mon_name = $this->month_pcre; + $day = '([0-9]{1,2})'; + $hour = $sec = $min = '([0-9]{2})'; + $year = '([0-9]{4})'; + $terminator = '\x0A?\x00?'; + $pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i'; + } + if (preg_match($pcre, $date, $match)) { + /* + Capturing subpatterns: + 1: Day name + 2: Month + 3: Day + 4: Hour + 5: Minute + 6: Second + 7: Year + */ + + $month = $this->month[strtolower($match[2])]; + return gmmktime((int) $match[4], (int) $match[5], (int) $match[6], $month, (int) $match[3], (int) $match[7]); + } + + return false; + } + + /** + * Parse dates using strtotime() + * + * @access protected + * @param string $date + * @return int|false Timestamp + */ + public function date_strtotime(string $date) + { + $strtotime = strtotime($date); + if ($strtotime === -1 || $strtotime === false) { + return false; + } + + return $strtotime; + } +} + +class_alias('SimplePie\Parse\Date', 'SimplePie_Parse_Date'); + +} + +namespace SimplePie { + +use SimplePie\XML\Declaration\Parser as DeclarationParser; +use XMLParser; + +/** + * Parses XML into something sane + * + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_parser_class()} + */ +class Parser implements RegistryAware +{ + /** @var int */ + public $error_code; + /** @var string */ + public $error_string; + /** @var int */ + public $current_line; + /** @var int */ + public $current_column; + /** @var int */ + public $current_byte; + /** @var string */ + public $separator = ' '; + /** @var string[] */ + public $namespace = ['']; + /** @var string[] */ + public $element = ['']; + /** @var string[] */ + public $xml_base = ['']; + /** @var bool[] */ + public $xml_base_explicit = [false]; + /** @var string[] */ + public $xml_lang = ['']; + /** @var array<string, mixed> */ + public $data = []; + /** @var array<array<string, mixed>> */ + public $datas = [[]]; + /** @var int */ + public $current_xhtml_construct = -1; + /** @var string */ + public $encoding; + /** @var Registry */ + protected $registry; + + /** + * @return void + */ + public function set_registry(\SimplePie\Registry $registry) + { + $this->registry = $registry; + } + + /** + * @return bool + */ + public function parse(string &$data, string $encoding, string $url = '') + { + if (class_exists('DOMXpath') && function_exists('Mf2\parse')) { + $doc = new \DOMDocument(); + @$doc->loadHTML($data); + $xpath = new \DOMXpath($doc); + // Check for both h-feed and h-entry, as both a feed with no entries + // and a list of entries without an h-feed wrapper are both valid. + $query = '//*[contains(concat(" ", @class, " "), " h-feed ") or '. + 'contains(concat(" ", @class, " "), " h-entry ")]'; + /** @var \DOMNodeList<\DOMElement> $result */ + $result = $xpath->query($query); + if ($result->length !== 0) { + return $this->parse_microformats($data, $url); + } + } + + // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character + if (strtoupper($encoding) === 'US-ASCII') { + $this->encoding = 'UTF-8'; + } else { + $this->encoding = $encoding; + } + + // Strip BOM: + // UTF-32 Big Endian BOM + if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") { + $data = substr($data, 4); + } + // UTF-32 Little Endian BOM + elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") { + $data = substr($data, 4); + } + // UTF-16 Big Endian BOM + elseif (substr($data, 0, 2) === "\xFE\xFF") { + $data = substr($data, 2); + } + // UTF-16 Little Endian BOM + elseif (substr($data, 0, 2) === "\xFF\xFE") { + $data = substr($data, 2); + } + // UTF-8 BOM + elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") { + $data = substr($data, 3); + } + + if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false) { + $declaration = $this->registry->create(DeclarationParser::class, [substr($data, 5, $pos - 5)]); + if ($declaration->parse()) { + $data = substr($data, $pos + 2); + $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . "\n" . + self::set_doctype($data); + } else { + $this->error_string = 'SimplePie bug! Please report this!'; + return false; + } + } else { + $data = self::set_doctype($data); + } + + $return = true; + + static $xml_is_sane = null; + if ($xml_is_sane === null) { + $parser_check = xml_parser_create(); + xml_parse_into_struct($parser_check, '<foo>&</foo>', $values); + if (\PHP_VERSION_ID < 80000) { + xml_parser_free($parser_check); + } + $xml_is_sane = isset($values[0]['value']); + } + + // Create the parser + if ($xml_is_sane) { + $xml = xml_parser_create_ns($this->encoding, $this->separator); + xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1); + xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0); + xml_set_character_data_handler($xml, [$this, 'cdata']); + xml_set_element_handler($xml, [$this, 'tag_open'], [$this, 'tag_close']); + + // Parse! + $wrapper = @is_writable(sys_get_temp_dir()) ? 'php://temp' : 'php://memory'; + if (($stream = fopen($wrapper, 'r+')) && + fwrite($stream, $data) && + rewind($stream)) { + //Parse by chunks not to use too much memory + do { + $stream_data = (string) fread($stream, 1048576); + + if (!xml_parse($xml, $stream_data, feof($stream))) { + $this->error_code = xml_get_error_code($xml); + $this->error_string = xml_error_string($this->error_code) ?: "Unknown"; + $return = false; + break; + } + } while (!feof($stream)); + fclose($stream); + } else { + $return = false; + } + + $this->current_line = xml_get_current_line_number($xml); + $this->current_column = xml_get_current_column_number($xml); + $this->current_byte = xml_get_current_byte_index($xml); + if (\PHP_VERSION_ID < 80000) { + xml_parser_free($xml); + } + return $return; + } + + libxml_clear_errors(); + $xml = new \XMLReader(); + $xml->xml($data); + while (@$xml->read()) { + switch ($xml->nodeType) { + case \XMLReader::END_ELEMENT: + if ($xml->namespaceURI !== '') { + $tagName = $xml->namespaceURI . $this->separator . $xml->localName; + } else { + $tagName = $xml->localName; + } + $this->tag_close(null, $tagName); + break; + case \XMLReader::ELEMENT: + $empty = $xml->isEmptyElement; + if ($xml->namespaceURI !== '') { + $tagName = $xml->namespaceURI . $this->separator . $xml->localName; + } else { + $tagName = $xml->localName; + } + $attributes = []; + while ($xml->moveToNextAttribute()) { + if ($xml->namespaceURI !== '') { + $attrName = $xml->namespaceURI . $this->separator . $xml->localName; + } else { + $attrName = $xml->localName; + } + $attributes[$attrName] = $xml->value; + } + $this->tag_open(null, $tagName, $attributes); + if ($empty) { + $this->tag_close(null, $tagName); + } + break; + case \XMLReader::TEXT: + + case \XMLReader::CDATA: + $this->cdata(null, $xml->value); + break; + } + } + if ($error = libxml_get_last_error()) { + $this->error_code = $error->code; + $this->error_string = $error->message; + $this->current_line = $error->line; + $this->current_column = $error->column; + return false; + } + + return true; + } + + /** + * @return int + */ + public function get_error_code() + { + return $this->error_code; + } + + /** + * @return string + */ + public function get_error_string() + { + return $this->error_string; + } + + /** + * @return int + */ + public function get_current_line() + { + return $this->current_line; + } + + /** + * @return int + */ + public function get_current_column() + { + return $this->current_column; + } + + /** + * @return int + */ + public function get_current_byte() + { + return $this->current_byte; + } + + /** + * @return array<string, mixed> + */ + public function get_data() + { + return $this->data; + } + + /** + * @param XMLParser|resource|null $parser + * @param array<string, string> $attributes + * @return void + */ + public function tag_open($parser, string $tag, array $attributes) + { + [$this->namespace[], $this->element[]] = $this->split_ns($tag); + + $attribs = []; + foreach ($attributes as $name => $value) { + [$attrib_namespace, $attribute] = $this->split_ns($name); + $attribs[$attrib_namespace][$attribute] = $value; + } + + if (isset($attribs[\SimplePie\SimplePie::NAMESPACE_XML]['base'])) { + $base = $this->registry->call(Misc::class, 'absolutize_url', [$attribs[\SimplePie\SimplePie::NAMESPACE_XML]['base'], end($this->xml_base)]); + if ($base !== false) { + $this->xml_base[] = $base; + $this->xml_base_explicit[] = true; + } + } else { + $this->xml_base[] = end($this->xml_base) ?: ''; + $this->xml_base_explicit[] = end($this->xml_base_explicit); + } + + if (isset($attribs[\SimplePie\SimplePie::NAMESPACE_XML]['lang'])) { + $this->xml_lang[] = $attribs[\SimplePie\SimplePie::NAMESPACE_XML]['lang']; + } else { + $this->xml_lang[] = end($this->xml_lang) ?: ''; + } + + if ($this->current_xhtml_construct >= 0) { + $this->current_xhtml_construct++; + if (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_XHTML) { + $this->data['data'] .= '<' . end($this->element); + if (isset($attribs[''])) { + foreach ($attribs[''] as $name => $value) { + $this->data['data'] .= ' ' . $name . '="' . htmlspecialchars($value, ENT_COMPAT, $this->encoding) . '"'; + } + } + $this->data['data'] .= '>'; + } + } else { + $this->datas[] = &$this->data; + $this->data = &$this->data['child'][end($this->namespace)][end($this->element)][]; + $this->data = ['data' => '', 'attribs' => $attribs, 'xml_base' => end($this->xml_base), 'xml_base_explicit' => end($this->xml_base_explicit), 'xml_lang' => end($this->xml_lang)]; + if ((end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_ATOM_03 && in_array(end($this->element), ['title', 'tagline', 'copyright', 'info', 'summary', 'content']) && isset($attribs['']['mode']) && $attribs['']['mode'] === 'xml') + || (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_ATOM_10 && in_array(end($this->element), ['rights', 'subtitle', 'summary', 'info', 'title', 'content']) && isset($attribs['']['type']) && $attribs['']['type'] === 'xhtml') + || (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_RSS_20 && in_array(end($this->element), ['title'])) + || (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_RSS_090 && in_array(end($this->element), ['title'])) + || (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_RSS_10 && in_array(end($this->element), ['title']))) { + $this->current_xhtml_construct = 0; + } + } + } + + /** + * @param XMLParser|resource|null $parser + * @return void + */ + public function cdata($parser, string $cdata) + { + if ($this->current_xhtml_construct >= 0) { + $this->data['data'] .= htmlspecialchars($cdata, ENT_QUOTES, $this->encoding); + } else { + $this->data['data'] .= $cdata; + } + } + + /** + * @param XMLParser|resource|null $parser + * @return void + */ + public function tag_close($parser, string $tag) + { + if ($this->current_xhtml_construct >= 0) { + $this->current_xhtml_construct--; + if (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_XHTML && !in_array(end($this->element), ['area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param'])) { + $this->data['data'] .= '</' . end($this->element) . '>'; + } + } + if ($this->current_xhtml_construct === -1) { + $this->data = &$this->datas[count($this->datas) - 1]; + array_pop($this->datas); + } + + array_pop($this->element); + array_pop($this->namespace); + array_pop($this->xml_base); + array_pop($this->xml_base_explicit); + array_pop($this->xml_lang); + } + + /** + * @return array{string, string} + */ + public function split_ns(string $string) + { + static $cache = []; + if (!isset($cache[$string])) { + if ($pos = strpos($string, $this->separator)) { + static $separator_length; + if (!$separator_length) { + $separator_length = strlen($this->separator); + } + $namespace = substr($string, 0, $pos); + $local_name = substr($string, $pos + $separator_length); + if (strtolower($namespace) === \SimplePie\SimplePie::NAMESPACE_ITUNES) { + $namespace = \SimplePie\SimplePie::NAMESPACE_ITUNES; + } + + // Normalize the Media RSS namespaces + if ($namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG || + $namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG2 || + $namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG3 || + $namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG4 || + $namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG5) { + $namespace = \SimplePie\SimplePie::NAMESPACE_MEDIARSS; + } + $cache[$string] = [$namespace, $local_name]; + } else { + $cache[$string] = ['', $string]; + } + } + return $cache[$string]; + } + + /** + * @param array<string, mixed> $data + */ + private function parse_hcard(array $data, bool $category = false): string + { + $name = ''; + $link = ''; + // Check if h-card is set and pass that information on in the link. + if (isset($data['type']) && in_array('h-card', $data['type'])) { + if (isset($data['properties']['name'][0])) { + $name = $data['properties']['name'][0]; + } + if (isset($data['properties']['url'][0])) { + $link = $data['properties']['url'][0]; + if ($name === '') { + $name = $link; + } else { + // can't have commas in categories. + $name = str_replace(',', '', $name); + } + $person_tag = $category ? '<span class="person-tag"></span>' : ''; + return '<a class="h-card" href="'.$link.'">'.$person_tag.$name.'</a>'; + } + } + return $data['value'] ?? ''; + } + + /** + * @return true + */ + private function parse_microformats(string &$data, string $url): bool + { + // For PHPStan, we already check that in call site. + \assert(function_exists('Mf2\parse')); + \assert(function_exists('Mf2\fetch')); + $feed_title = ''; + $feed_author = null; + $author_cache = []; + $items = []; + $entries = []; + $mf = \Mf2\parse($data, $url); + // First look for an h-feed. + $h_feed = []; + foreach ($mf['items'] as $mf_item) { + if (in_array('h-feed', $mf_item['type'])) { + $h_feed = $mf_item; + break; + } + // Also look for h-feed or h-entry in the children of each top level item. + if (!isset($mf_item['children'][0]['type'])) { + continue; + } + if (in_array('h-feed', $mf_item['children'][0]['type'])) { + $h_feed = $mf_item['children'][0]; + // In this case the parent of the h-feed may be an h-card, so use it as + // the feed_author. + if (in_array('h-card', $mf_item['type'])) { + $feed_author = $mf_item; + } + break; + } elseif (in_array('h-entry', $mf_item['children'][0]['type'])) { + $entries = $mf_item['children']; + // In this case the parent of the h-entry list may be an h-card, so use + // it as the feed_author. + if (in_array('h-card', $mf_item['type'])) { + $feed_author = $mf_item; + } + break; + } + } + if (isset($h_feed['children'])) { + $entries = $h_feed['children']; + // Also set the feed title and store author from the h-feed if available. + if (isset($mf['items'][0]['properties']['name'][0])) { + $feed_title = $mf['items'][0]['properties']['name'][0]; + } + if (isset($mf['items'][0]['properties']['author'][0])) { + $feed_author = $mf['items'][0]['properties']['author'][0]; + } + } elseif (count($entries) === 0) { + $entries = $mf['items']; + } + for ($i = 0; $i < count($entries); $i++) { + $entry = $entries[$i]; + if (in_array('h-entry', $entry['type'])) { + $item = []; + $title = ''; + $description = ''; + if (isset($entry['properties']['url'][0])) { + $link = $entry['properties']['url'][0]; + if (isset($link['value'])) { + $link = $link['value']; + } + $item['link'] = [['data' => $link]]; + } + if (isset($entry['properties']['uid'][0])) { + $guid = $entry['properties']['uid'][0]; + if (isset($guid['value'])) { + $guid = $guid['value']; + } + $item['guid'] = [['data' => $guid]]; + } + if (isset($entry['properties']['name'][0])) { + $title = $entry['properties']['name'][0]; + if (isset($title['value'])) { + $title = $title['value']; + } + $item['title'] = [['data' => $title]]; + } + if (isset($entry['properties']['author'][0]) || isset($feed_author)) { + // author is a special case, it can be plain text or an h-card array. + // If it's plain text it can also be a url that should be followed to + // get the actual h-card. + $author = $entry['properties']['author'][0] ?? $feed_author; + if (!is_string($author)) { + $author = $this->parse_hcard($author); + } elseif (strpos($author, 'http') === 0) { + if (isset($author_cache[$author])) { + $author = $author_cache[$author]; + } else { + if ($mf = \Mf2\fetch($author)) { + foreach ($mf['items'] as $hcard) { + // Only interested in an h-card by itself in this case. + if (!in_array('h-card', $hcard['type'])) { + continue; + } + // It must have a url property matching what we fetched. + if (!isset($hcard['properties']['url']) || + !(in_array($author, $hcard['properties']['url']))) { + continue; + } + // Save parse_hcard the trouble of finding the correct url. + $hcard['properties']['url'][0] = $author; + // Cache this h-card for the next h-entry to check. + $author_cache[$author] = $this->parse_hcard($hcard); + $author = $author_cache[$author]; + break; + } + } + } + } + $item['author'] = [['data' => $author]]; + } + if (isset($entry['properties']['photo'][0])) { + // If a photo is also in content, don't need to add it again here. + $content = ''; + if (isset($entry['properties']['content'][0]['html'])) { + $content = $entry['properties']['content'][0]['html']; + } + $photo_list = []; + for ($j = 0; $j < count($entry['properties']['photo']); $j++) { + $photo = $entry['properties']['photo'][$j]; + if (!empty($photo) && strpos($content, $photo) === false) { + $photo_list[] = $photo; + } + } + // When there's more than one photo show the first and use a lightbox. + // Need a permanent, unique name for the image set, but don't have + // anything unique except for the content itself, so use that. + $count = count($photo_list); + if ($count > 1) { + $image_set_id = preg_replace('/[[:^alnum:]]/', '', $photo_list[0]); + $description = '<p>'; + for ($j = 0; $j < $count; $j++) { + $hidden = $j === 0 ? '' : 'class="hidden" '; + $description .= '<a href="'.$photo_list[$j].'" '.$hidden. + 'data-lightbox="image-set-'.$image_set_id.'">'. + '<img src="'.$photo_list[$j].'"></a>'; + } + $description .= '<br><b>'.$count.' photos</b></p>'; + } elseif ($count == 1) { + $description = '<p><img src="'.$photo_list[0].'"></p>'; + } + } + if (isset($entry['properties']['content'][0]['html'])) { + // e-content['value'] is the same as p-name when they are on the same + // element. Use this to replace title with a strip_tags version so + // that alt text from images is not included in the title. + if ($entry['properties']['content'][0]['value'] === $title) { + $title = strip_tags($entry['properties']['content'][0]['html']); + $item['title'] = [['data' => $title]]; + } + $description .= $entry['properties']['content'][0]['html']; + if (isset($entry['properties']['in-reply-to'][0])) { + $in_reply_to = ''; + if (is_string($entry['properties']['in-reply-to'][0])) { + $in_reply_to = $entry['properties']['in-reply-to'][0]; + } elseif (isset($entry['properties']['in-reply-to'][0]['value'])) { + $in_reply_to = $entry['properties']['in-reply-to'][0]['value']; + } + if ($in_reply_to !== '') { + $description .= '<p><span class="in-reply-to"></span> '. + '<a href="'.$in_reply_to.'">'.$in_reply_to.'</a><p>'; + } + } + $item['description'] = [['data' => $description]]; + } + if (isset($entry['properties']['category'])) { + $category_csv = ''; + // Categories can also contain h-cards. + foreach ($entry['properties']['category'] as $category) { + if ($category_csv !== '') { + $category_csv .= ', '; + } + if (is_string($category)) { + // Can't have commas in categories. + $category_csv .= str_replace(',', '', $category); + } else { + $category_csv .= $this->parse_hcard($category, true); + } + } + $item['category'] = [['data' => $category_csv]]; + } + if (isset($entry['properties']['published'][0])) { + $timestamp = strtotime($entry['properties']['published'][0]); + $pub_date = date('F j Y g:ia', $timestamp).' GMT'; + $item['pubDate'] = [['data' => $pub_date]]; + } + // The title and description are set to the empty string to represent + // a deleted item (which also makes it an invalid rss item). + if (isset($entry['properties']['deleted'][0])) { + $item['title'] = [['data' => '']]; + $item['description'] = [['data' => '']]; + } + $items[] = ['child' => ['' => $item]]; + } + } + // Mimic RSS data format when storing microformats. + $link = [['data' => $url]]; + $image = ''; + if (!is_string($feed_author) && + isset($feed_author['properties']['photo'][0])) { + $image = [['child' => ['' => ['url' => + [['data' => $feed_author['properties']['photo'][0]]]]]]]; + } + // Use the name given for the h-feed, or get the title from the html. + if ($feed_title !== '') { + $feed_title = [['data' => htmlspecialchars($feed_title)]]; + } elseif ($position = strpos($data, '<title>')) { + $start = $position < 200 ? 0 : $position - 200; + $check = substr($data, $start, 400); + $matches = []; + if (preg_match('/<title>(.+)<\/title>/', $check, $matches)) { + $feed_title = [['data' => htmlspecialchars($matches[1])]]; + } + } + $channel = ['channel' => [['child' => ['' => + ['link' => $link, 'image' => $image, 'title' => $feed_title, + 'item' => $items]]]]]; + $rss = [['attribs' => ['' => ['version' => '2.0']], + 'child' => ['' => $channel]]]; + $this->data = ['child' => ['' => ['rss' => $rss]]]; + return true; + } + + private static function set_doctype(string $data): string + { + // Strip DOCTYPE except if containing an [internal subset] + $data = preg_replace('/^\\s*<!DOCTYPE\\s[^>\\[\\]]*>\s*/', '', $data) ?? $data; + // Declare HTML entities only if no remaining DOCTYPE + $doctype = preg_match('/^\\s*<!DOCTYPE\\s/', $data) ? '' : self::declare_html_entities(); + return $doctype . $data; + } + + private static function declare_html_entities(): string + { + // This is required because the RSS specification says that entity-encoded + // html is allowed, but the xml specification says they must be declared. + return '<!DOCTYPE rss [ <!ENTITY nbsp " "> <!ENTITY iexcl "¡"> <!ENTITY cent "¢"> <!ENTITY pound "£"> <!ENTITY curren "¤"> <!ENTITY yen "¥"> <!ENTITY brvbar "¦"> <!ENTITY sect "§"> <!ENTITY uml "¨"> <!ENTITY copy "©"> <!ENTITY ordf "ª"> <!ENTITY laquo "«"> <!ENTITY not "¬"> <!ENTITY shy "­"> <!ENTITY reg "®"> <!ENTITY macr "¯"> <!ENTITY deg "°"> <!ENTITY plusmn "±"> <!ENTITY sup2 "²"> <!ENTITY sup3 "³"> <!ENTITY acute "´"> <!ENTITY micro "µ"> <!ENTITY para "¶"> <!ENTITY middot "·"> <!ENTITY cedil "¸"> <!ENTITY sup1 "¹"> <!ENTITY ordm "º"> <!ENTITY raquo "»"> <!ENTITY frac14 "¼"> <!ENTITY frac12 "½"> <!ENTITY frac34 "¾"> <!ENTITY iquest "¿"> <!ENTITY Agrave "À"> <!ENTITY Aacute "Á"> <!ENTITY Acirc "Â"> <!ENTITY Atilde "Ã"> <!ENTITY Auml "Ä"> <!ENTITY Aring "Å"> <!ENTITY AElig "Æ"> <!ENTITY Ccedil "Ç"> <!ENTITY Egrave "È"> <!ENTITY Eacute "É"> <!ENTITY Ecirc "Ê"> <!ENTITY Euml "Ë"> <!ENTITY Igrave "Ì"> <!ENTITY Iacute "Í"> <!ENTITY Icirc "Î"> <!ENTITY Iuml "Ï"> <!ENTITY ETH "Ð"> <!ENTITY Ntilde "Ñ"> <!ENTITY Ograve "Ò"> <!ENTITY Oacute "Ó"> <!ENTITY Ocirc "Ô"> <!ENTITY Otilde "Õ"> <!ENTITY Ouml "Ö"> <!ENTITY times "×"> <!ENTITY Oslash "Ø"> <!ENTITY Ugrave "Ù"> <!ENTITY Uacute "Ú"> <!ENTITY Ucirc "Û"> <!ENTITY Uuml "Ü"> <!ENTITY Yacute "Ý"> <!ENTITY THORN "Þ"> <!ENTITY szlig "ß"> <!ENTITY agrave "à"> <!ENTITY aacute "á"> <!ENTITY acirc "â"> <!ENTITY atilde "ã"> <!ENTITY auml "ä"> <!ENTITY aring "å"> <!ENTITY aelig "æ"> <!ENTITY ccedil "ç"> <!ENTITY egrave "è"> <!ENTITY eacute "é"> <!ENTITY ecirc "ê"> <!ENTITY euml "ë"> <!ENTITY igrave "ì"> <!ENTITY iacute "í"> <!ENTITY icirc "î"> <!ENTITY iuml "ï"> <!ENTITY eth "ð"> <!ENTITY ntilde "ñ"> <!ENTITY ograve "ò"> <!ENTITY oacute "ó"> <!ENTITY ocirc "ô"> <!ENTITY otilde "õ"> <!ENTITY ouml "ö"> <!ENTITY divide "÷"> <!ENTITY oslash "ø"> <!ENTITY ugrave "ù"> <!ENTITY uacute "ú"> <!ENTITY ucirc "û"> <!ENTITY uuml "ü"> <!ENTITY yacute "ý"> <!ENTITY thorn "þ"> <!ENTITY yuml "ÿ"> <!ENTITY OElig "Œ"> <!ENTITY oelig "œ"> <!ENTITY Scaron "Š"> <!ENTITY scaron "š"> <!ENTITY Yuml "Ÿ"> <!ENTITY fnof "ƒ"> <!ENTITY circ "ˆ"> <!ENTITY tilde "˜"> <!ENTITY Alpha "Α"> <!ENTITY Beta "Β"> <!ENTITY Gamma "Γ"> <!ENTITY Epsilon "Ε"> <!ENTITY Zeta "Ζ"> <!ENTITY Eta "Η"> <!ENTITY Theta "Θ"> <!ENTITY Iota "Ι"> <!ENTITY Kappa "Κ"> <!ENTITY Lambda "Λ"> <!ENTITY Mu "Μ"> <!ENTITY Nu "Ν"> <!ENTITY Xi "Ξ"> <!ENTITY Omicron "Ο"> <!ENTITY Pi "Π"> <!ENTITY Rho "Ρ"> <!ENTITY Sigma "Σ"> <!ENTITY Tau "Τ"> <!ENTITY Upsilon "Υ"> <!ENTITY Phi "Φ"> <!ENTITY Chi "Χ"> <!ENTITY Psi "Ψ"> <!ENTITY Omega "Ω"> <!ENTITY alpha "α"> <!ENTITY beta "β"> <!ENTITY gamma "γ"> <!ENTITY delta "δ"> <!ENTITY epsilon "ε"> <!ENTITY zeta "ζ"> <!ENTITY eta "η"> <!ENTITY theta "θ"> <!ENTITY iota "ι"> <!ENTITY kappa "κ"> <!ENTITY lambda "λ"> <!ENTITY mu "μ"> <!ENTITY nu "ν"> <!ENTITY xi "ξ"> <!ENTITY omicron "ο"> <!ENTITY pi "π"> <!ENTITY rho "ρ"> <!ENTITY sigmaf "ς"> <!ENTITY sigma "σ"> <!ENTITY tau "τ"> <!ENTITY upsilon "υ"> <!ENTITY phi "φ"> <!ENTITY chi "χ"> <!ENTITY psi "ψ"> <!ENTITY omega "ω"> <!ENTITY thetasym "ϑ"> <!ENTITY upsih "ϒ"> <!ENTITY piv "ϖ"> <!ENTITY ensp " "> <!ENTITY emsp " "> <!ENTITY thinsp " "> <!ENTITY zwnj "‌"> <!ENTITY zwj "‍"> <!ENTITY lrm "‎"> <!ENTITY rlm "‏"> <!ENTITY ndash "–"> <!ENTITY mdash "—"> <!ENTITY lsquo "‘"> <!ENTITY rsquo "’"> <!ENTITY sbquo "‚"> <!ENTITY ldquo "“"> <!ENTITY rdquo "”"> <!ENTITY bdquo "„"> <!ENTITY dagger "†"> <!ENTITY Dagger "‡"> <!ENTITY bull "•"> <!ENTITY hellip "…"> <!ENTITY permil "‰"> <!ENTITY prime "′"> <!ENTITY Prime "″"> <!ENTITY lsaquo "‹"> <!ENTITY rsaquo "›"> <!ENTITY oline "‾"> <!ENTITY frasl "⁄"> <!ENTITY euro "€"> <!ENTITY image "ℑ"> <!ENTITY weierp "℘"> <!ENTITY real "ℜ"> <!ENTITY trade "™"> <!ENTITY alefsym "ℵ"> <!ENTITY larr "←"> <!ENTITY uarr "↑"> <!ENTITY rarr "→"> <!ENTITY darr "↓"> <!ENTITY harr "↔"> <!ENTITY crarr "↵"> <!ENTITY lArr "⇐"> <!ENTITY uArr "⇑"> <!ENTITY rArr "⇒"> <!ENTITY dArr "⇓"> <!ENTITY hArr "⇔"> <!ENTITY forall "∀"> <!ENTITY part "∂"> <!ENTITY exist "∃"> <!ENTITY empty "∅"> <!ENTITY nabla "∇"> <!ENTITY isin "∈"> <!ENTITY notin "∉"> <!ENTITY ni "∋"> <!ENTITY prod "∏"> <!ENTITY sum "∑"> <!ENTITY minus "−"> <!ENTITY lowast "∗"> <!ENTITY radic "√"> <!ENTITY prop "∝"> <!ENTITY infin "∞"> <!ENTITY ang "∠"> <!ENTITY and "∧"> <!ENTITY or "∨"> <!ENTITY cap "∩"> <!ENTITY cup "∪"> <!ENTITY int "∫"> <!ENTITY there4 "∴"> <!ENTITY sim "∼"> <!ENTITY cong "≅"> <!ENTITY asymp "≈"> <!ENTITY ne "≠"> <!ENTITY equiv "≡"> <!ENTITY le "≤"> <!ENTITY ge "≥"> <!ENTITY sub "⊂"> <!ENTITY sup "⊃"> <!ENTITY nsub "⊄"> <!ENTITY sube "⊆"> <!ENTITY supe "⊇"> <!ENTITY oplus "⊕"> <!ENTITY otimes "⊗"> <!ENTITY perp "⊥"> <!ENTITY sdot "⋅"> <!ENTITY lceil "⌈"> <!ENTITY rceil "⌉"> <!ENTITY lfloor "⌊"> <!ENTITY rfloor "⌋"> <!ENTITY lang "〈"> <!ENTITY rang "〉"> <!ENTITY loz "◊"> <!ENTITY spades "♠"> <!ENTITY clubs "♣"> <!ENTITY hearts "♥"> <!ENTITY diams "♦"> ]>'; + } +} + +class_alias('SimplePie\Parser', 'SimplePie_Parser'); + +} + +namespace SimplePie { + +/** + * Handles `<media:rating>` or `<itunes:explicit>` tags as defined in Media RSS and iTunes RSS respectively + * + * Used by {@see \SimplePie\Enclosure::get_rating()} and {@see \SimplePie\Enclosure::get_ratings()} + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_rating_class()} + */ +class Rating +{ + /** + * Rating scheme + * + * @var ?string + * @see get_scheme() + */ + public $scheme; + + /** + * Rating value + * + * @var ?string + * @see get_value() + */ + public $value; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + */ + public function __construct( + ?string $scheme = null, + ?string $value = null + ) { + $this->scheme = $scheme; + $this->value = $value; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the organizational scheme for the rating + * + * @return string|null + */ + public function get_scheme() + { + if ($this->scheme !== null) { + return $this->scheme; + } + + return null; + } + + /** + * Get the value of the rating + * + * @return string|null + */ + public function get_value() + { + if ($this->value !== null) { + return $this->value; + } + + return null; + } +} + +class_alias('SimplePie\Rating', 'SimplePie_Rating'); + +} + +namespace SimplePie { + +use InvalidArgumentException; +use SimplePie\Content\Type\Sniffer; +use SimplePie\Parse\Date; +use SimplePie\XML\Declaration\Parser as DeclarationParser; + +/** + * Handles creating objects and calling methods + * + * Access this via {@see \SimplePie\SimplePie::get_registry()} + */ +class Registry +{ + /** + * Default class mapping + * + * Overriding classes *must* subclass these. + * + * @var array<class-string, class-string> + */ + protected $default = [ + Cache::class => Cache::class, + Locator::class => Locator::class, + Parser::class => Parser::class, + File::class => File::class, + Sanitize::class => Sanitize::class, + Item::class => Item::class, + Author::class => Author::class, + Category::class => Category::class, + Enclosure::class => Enclosure::class, + Caption::class => Caption::class, + Copyright::class => Copyright::class, + Credit::class => Credit::class, + Rating::class => Rating::class, + Restriction::class => Restriction::class, + Sniffer::class => Sniffer::class, + Source::class => Source::class, + Misc::class => Misc::class, + DeclarationParser::class => DeclarationParser::class, + Date::class => Date::class, + ]; + + /** + * Class mapping + * + * @see register() + * @var array<string, class-string> + */ + protected $classes = []; + + /** + * Legacy classes + * + * @see register() + * @var array<class-string> + */ + protected $legacy = []; + + /** + * Legacy types + * + * @see register() + * @var array<string, class-string> + */ + private $legacyTypes = [ + 'Cache' => Cache::class, + 'Locator' => Locator::class, + 'Parser' => Parser::class, + 'File' => File::class, + 'Sanitize' => Sanitize::class, + 'Item' => Item::class, + 'Author' => Author::class, + 'Category' => Category::class, + 'Enclosure' => Enclosure::class, + 'Caption' => Caption::class, + 'Copyright' => Copyright::class, + 'Credit' => Credit::class, + 'Rating' => Rating::class, + 'Restriction' => Restriction::class, + 'Content_Type_Sniffer' => Sniffer::class, + 'Source' => Source::class, + 'Misc' => Misc::class, + 'XML_Declaration_Parser' => DeclarationParser::class, + 'Parse_Date' => Date::class, + ]; + + /** + * Constructor + * + * No-op + */ + public function __construct() + { + } + + /** + * Register a class + * + * @param string $type See {@see $default} for names + * @param class-string $class Class name, must subclass the corresponding default + * @param bool $legacy Whether to enable legacy support for this class + * @return bool Successfulness + */ + public function register(string $type, $class, bool $legacy = false) + { + if (array_key_exists($type, $this->legacyTypes)) { + // trigger_error(sprintf('"%s"(): Using argument #1 ($type) with value "%s" is deprecated since SimplePie 1.8.0, use class-string "%s" instead.', __METHOD__, $type, $this->legacyTypes[$type]), \E_USER_DEPRECATED); + + $type = $this->legacyTypes[$type]; + } + + if (!array_key_exists($type, $this->default)) { + return false; + } + + if (!class_exists($class)) { + return false; + } + + /** @var string */ + $base_class = $this->default[$type]; + + if (!is_subclass_of($class, $base_class)) { + return false; + } + + $this->classes[$type] = $class; + + if ($legacy) { + $this->legacy[] = $class; + } + + return true; + } + + /** + * Get the class registered for a type + * + * Where possible, use {@see create()} or {@see call()} instead + * + * @template T + * @param class-string<T> $type + * @return class-string<T>|null + */ + public function get_class($type) + { + if (array_key_exists($type, $this->legacyTypes)) { + // trigger_error(sprintf('"%s"(): Using argument #1 ($type) with value "%s" is deprecated since SimplePie 1.8.0, use class-string "%s" instead.', __METHOD__, $type, $this->legacyTypes[$type]), \E_USER_DEPRECATED); + + $type = $this->legacyTypes[$type]; + } + + if (!array_key_exists($type, $this->default)) { + return null; + } + + // For PHPStan: values in $default should be subtypes of keys. + /** @var class-string<T> */ + $class = $this->default[$type]; + + if (array_key_exists($type, $this->classes)) { + // For PHPStan: values in $classes should be subtypes of keys. + /** @var class-string<T> */ + $class = $this->classes[$type]; + } + + return $class; + } + + /** + * Create a new instance of a given type + * + * @template T class-string $type + * @param class-string<T> $type + * @param array<mixed> $parameters Parameters to pass to the constructor + * @return T Instance of class + */ + public function &create($type, array $parameters = []) + { + $class = $this->get_class($type); + if ($class === null) { + throw new InvalidArgumentException(sprintf( + '%s(): Argument #1 ($type) "%s" not found in class list.', + __METHOD__, + $type + ), 1); + } + + if (!method_exists($class, '__construct')) { + $instance = new $class(); + } else { + $reflector = new \ReflectionClass($class); + // For PHPStan: $class is T. + /** @var T */ + $instance = $reflector->newInstanceArgs($parameters); + } + + if ($instance instanceof RegistryAware) { + $instance->set_registry($this); + } elseif (method_exists($instance, 'set_registry')) { + trigger_error(sprintf('Using the method "set_registry()" without implementing "%s" is deprecated since SimplePie 1.8.0, implement "%s" in "%s".', RegistryAware::class, RegistryAware::class, $class), \E_USER_DEPRECATED); + $instance->set_registry($this); + } + + return $instance; + } + + /** + * Call a static method for a type + * + * @param class-string $type + * @param string $method + * @param array<mixed> $parameters + * @return mixed + */ + public function &call($type, string $method, array $parameters = []) + { + $class = $this->get_class($type); + if ($class === null) { + throw new InvalidArgumentException(sprintf( + '%s(): Argument #1 ($type) "%s" not found in class list.', + __METHOD__, + $type + ), 1); + } + + if (in_array($class, $this->legacy)) { + switch ($type) { + case Cache::class: + // For backwards compatibility with old non-static + // Cache::create() methods in PHP < 8.0. + // No longer supported as of PHP 8.0. + if ($method === 'get_handler') { + // Fixing this PHPStan error breaks CacheTest::testDirectOverrideLegacy() + /** @phpstan-ignore argument.type */ + $result = @call_user_func_array([$class, 'create'], $parameters); + return $result; + } + break; + } + } + + $callable = [$class, $method]; + assert(is_callable($callable), 'For PHPstan'); + $result = call_user_func_array($callable, $parameters); + return $result; + } +} + +class_alias('SimplePie\Registry', 'SimplePie_Registry'); + +} + +namespace SimplePie { + +/** + * Handles the injection of Registry into other class + * + * {@see \SimplePie\SimplePie::get_registry()} + */ +interface RegistryAware +{ + /** + * Set the Registry into the class + * + * @return void + */ + public function set_registry(Registry $registry); +} + +} + +namespace SimplePie { + +/** + * Handles `<media:restriction>` as defined in Media RSS + * + * Used by {@see \SimplePie\Enclosure::get_restriction()} and {@see \SimplePie\Enclosure::get_restrictions()} + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_restriction_class()} + */ +class Restriction +{ + public const RELATIONSHIP_ALLOW = 'allow'; + public const RELATIONSHIP_DENY = 'deny'; + + /** + * Relationship ('allow'/'deny') + * + * @var self::RELATIONSHIP_*|null + * @see get_relationship() + */ + public $relationship; + + /** + * Type of restriction + * + * @var string|null + * @see get_type() + */ + public $type; + + /** + * Restricted values + * + * @var string|null + * @see get_value() + */ + public $value; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + * + * @param ?self::RELATIONSHIP_* $relationship + */ + public function __construct(?string $relationship = null, ?string $type = null, ?string $value = null) + { + $this->relationship = $relationship; + $this->type = $type; + $this->value = $value; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the relationship + * + * @return ?self::RELATIONSHIP_* + */ + public function get_relationship() + { + if ($this->relationship !== null) { + return $this->relationship; + } + + return null; + } + + /** + * Get the type + * + * @return string|null + */ + public function get_type() + { + if ($this->type !== null) { + return $this->type; + } + + return null; + } + + /** + * Get the list of restricted things + * + * @return string|null + */ + public function get_value() + { + if ($this->value !== null) { + return $this->value; + } + + return null; + } +} + +class_alias('SimplePie\Restriction', 'SimplePie_Restriction'); + +} + +namespace SimplePie { + +use DOMDocument; +use DOMXPath; +use InvalidArgumentException; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\UriFactoryInterface; +use SimplePie\Cache\Base; +use SimplePie\Cache\BaseDataCache; +use SimplePie\Cache\CallableNameFilter; +use SimplePie\Cache\DataCache; +use SimplePie\Cache\NameFilter; +use SimplePie\HTTP\Client; +use SimplePie\HTTP\ClientException; +use SimplePie\HTTP\FileClient; +use SimplePie\HTTP\Psr18Client; + +/** + * Used for data cleanup and post-processing + * + * + * This class can be overloaded with {@see \SimplePie\SimplePie::set_sanitize_class()} + * + * @todo Move to using an actual HTML parser (this will allow tags to be properly stripped, and to switch between HTML and XHTML), this will also make it easier to shorten a string while preserving HTML tags + */ +class Sanitize implements RegistryAware +{ + // Private vars + /** @var string */ + public $base = ''; + + // Options + /** @var bool */ + public $remove_div = true; + /** @var string */ + public $image_handler = ''; + /** @var string[] */ + public $strip_htmltags = ['base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style']; + /** @var bool */ + public $encode_instead_of_strip = false; + /** @var string[] */ + public $strip_attributes = ['bgsound', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc']; + /** @var string[] */ + public $rename_attributes = []; + /** @var array<string, array<string, string>> */ + public $add_attributes = ['audio' => ['preload' => 'none'], 'iframe' => ['sandbox' => 'allow-scripts allow-same-origin'], 'video' => ['preload' => 'none']]; + /** @var bool */ + public $strip_comments = false; + /** @var string */ + public $output_encoding = 'UTF-8'; + /** @var bool */ + public $enable_cache = true; + /** @var string */ + public $cache_location = './cache'; + /** @var string&(callable(string): string) */ + public $cache_name_function = 'md5'; + + /** + * @var NameFilter + */ + private $cache_namefilter; + /** @var int */ + public $timeout = 10; + /** @var string */ + public $useragent = ''; + /** @var bool */ + public $force_fsockopen = false; + /** @var array<string, string|string[]> */ + public $replace_url_attributes = []; + /** + * @var array<int, mixed> Custom curl options + * @see SimplePie::set_curl_options() + */ + private $curl_options = []; + + /** @var Registry */ + public $registry; + + /** + * @var DataCache|null + */ + private $cache = null; + + /** + * @var int Cache duration (in seconds) + */ + private $cache_duration = 3600; + + /** + * List of domains for which to force HTTPS. + * @see \SimplePie\Sanitize::set_https_domains() + * Array is a tree split at DNS levels. Example: + * array('biz' => true, 'com' => array('example' => true), 'net' => array('example' => array('www' => true))) + * @var true|array<string, true|array<string, true|array<string, array<string, true|array<string, true|array<string, true>>>>>> + */ + public $https_domains = []; + + /** + * @var Client|null + */ + private $http_client = null; + + public function __construct() + { + // Set defaults + $this->set_url_replacements(null); + } + + /** + * @return void + */ + public function remove_div(bool $enable = true) + { + $this->remove_div = (bool) $enable; + } + + /** + * @param string|false $page + * @return void + */ + public function set_image_handler($page = false) + { + if ($page) { + $this->image_handler = (string) $page; + } else { + $this->image_handler = ''; + } + } + + /** + * @return void + */ + public function set_registry(\SimplePie\Registry $registry) + { + $this->registry = $registry; + } + + /** + * @param (string&(callable(string): string))|NameFilter $cache_name_function + * @param class-string<Cache> $cache_class + * @return void + */ + public function pass_cache_data(bool $enable_cache = true, string $cache_location = './cache', $cache_name_function = 'md5', string $cache_class = Cache::class, ?DataCache $cache = null) + { + $this->enable_cache = $enable_cache; + + if ($cache_location) { + $this->cache_location = $cache_location; + } + + // @phpstan-ignore-next-line Enforce PHPDoc type. + if (!is_string($cache_name_function) && !$cache_name_function instanceof NameFilter) { + throw new InvalidArgumentException(sprintf( + '%s(): Argument #3 ($cache_name_function) must be of type %s', + __METHOD__, + NameFilter::class + ), 1); + } + + // BC: $cache_name_function could be a callable as string + if (is_string($cache_name_function)) { + // trigger_error(sprintf('Providing $cache_name_function as string in "%s()" is deprecated since SimplePie 1.8.0, provide as "%s" instead.', __METHOD__, NameFilter::class), \E_USER_DEPRECATED); + $this->cache_name_function = $cache_name_function; + + $cache_name_function = new CallableNameFilter($cache_name_function); + } + + $this->cache_namefilter = $cache_name_function; + + if ($cache !== null) { + $this->cache = $cache; + } + } + + /** + * Set a PSR-18 client and PSR-17 factories + * + * Allows you to use your own HTTP client implementations. + */ + final public function set_http_client( + ClientInterface $http_client, + RequestFactoryInterface $request_factory, + UriFactoryInterface $uri_factory + ): void { + $this->http_client = new Psr18Client($http_client, $request_factory, $uri_factory); + } + + /** + * @deprecated since SimplePie 1.9.0, use \SimplePie\Sanitize::set_http_client() instead. + * @param class-string<File> $file_class + * @param array<int, mixed> $curl_options + * @return void + */ + public function pass_file_data(string $file_class = File::class, int $timeout = 10, string $useragent = '', bool $force_fsockopen = false, array $curl_options = []) + { + // trigger_error(sprintf('SimplePie\Sanitize::pass_file_data() is deprecated since SimplePie 1.9.0, please use "SimplePie\Sanitize::set_http_client()" instead.'), \E_USER_DEPRECATED); + if ($timeout) { + $this->timeout = $timeout; + } + + if ($useragent) { + $this->useragent = $useragent; + } + + if ($force_fsockopen) { + $this->force_fsockopen = $force_fsockopen; + } + + $this->curl_options = $curl_options; + // Invalidate the registered client. + $this->http_client = null; + } + + /** + * @param string[]|string|false $tags Set a list of tags to strip, or set empty string to use default tags, or false to strip nothing. + * @return void + */ + public function strip_htmltags($tags = ['base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style']) + { + if ($tags) { + if (is_array($tags)) { + $this->strip_htmltags = $tags; + } else { + $this->strip_htmltags = explode(',', $tags); + } + } else { + $this->strip_htmltags = []; + } + } + + /** + * @return void + */ + public function encode_instead_of_strip(bool $encode = false) + { + $this->encode_instead_of_strip = $encode; + } + + /** + * @param string[]|string $attribs + * @return void + */ + public function rename_attributes($attribs = []) + { + if ($attribs) { + if (is_array($attribs)) { + $this->rename_attributes = $attribs; + } else { + $this->rename_attributes = explode(',', $attribs); + } + } else { + $this->rename_attributes = []; + } + } + + /** + * @param string[]|string $attribs + * @return void + */ + public function strip_attributes($attribs = ['bgsound', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc']) + { + if ($attribs) { + if (is_array($attribs)) { + $this->strip_attributes = $attribs; + } else { + $this->strip_attributes = explode(',', $attribs); + } + } else { + $this->strip_attributes = []; + } + } + + /** + * @param array<string, array<string, string>> $attribs + * @return void + */ + public function add_attributes(array $attribs = ['audio' => ['preload' => 'none'], 'iframe' => ['sandbox' => 'allow-scripts allow-same-origin'], 'video' => ['preload' => 'none']]) + { + $this->add_attributes = $attribs; + } + + /** + * @return void + */ + public function strip_comments(bool $strip = false) + { + $this->strip_comments = $strip; + } + + /** + * @return void + */ + public function set_output_encoding(string $encoding = 'UTF-8') + { + $this->output_encoding = $encoding; + } + + /** + * Set element/attribute key/value pairs of HTML attributes + * containing URLs that need to be resolved relative to the feed + * + * Defaults to |a|@href, |area|@href, |audio|@src, |blockquote|@cite, + * |del|@cite, |form|@action, |img|@longdesc, |img|@src, |input|@src, + * |ins|@cite, |q|@cite, |source|@src, |video|@src + * + * @since 1.0 + * @param array<string, string|string[]>|null $element_attribute Element/attribute key/value pairs, null for default + * @return void + */ + public function set_url_replacements(?array $element_attribute = null) + { + if ($element_attribute === null) { + $element_attribute = [ + 'a' => 'href', + 'area' => 'href', + 'audio' => 'src', + 'blockquote' => 'cite', + 'del' => 'cite', + 'form' => 'action', + 'img' => [ + 'longdesc', + 'src' + ], + 'input' => 'src', + 'ins' => 'cite', + 'q' => 'cite', + 'source' => 'src', + 'video' => [ + 'poster', + 'src' + ] + ]; + } + $this->replace_url_attributes = $element_attribute; + } + + /** + * Set the list of domains for which to force HTTPS. + * @see \SimplePie\Misc::https_url() + * Example array('biz', 'example.com', 'example.org', 'www.example.net'); + * + * @param string[] $domains list of domain names ['biz', 'example.com', 'example.org', 'www.example.net'] + * + * @return void + */ + public function set_https_domains(array $domains) + { + $this->https_domains = []; + foreach ($domains as $domain) { + $domain = trim($domain, ". \t\n\r\0\x0B"); + $segments = array_reverse(explode('.', $domain)); + /** @var true|array<string, true|array<string, true|array<string, array<string, true|array<string, true|array<string, true>>>>>> */ // Needed for PHPStan. + $node = &$this->https_domains; + foreach ($segments as $segment) {//Build a tree + if ($node === true) { + break; + } + if (!isset($node[$segment])) { + $node[$segment] = []; + } + $node = &$node[$segment]; + } + $node = true; + } + } + + /** + * Check if the domain is in the list of forced HTTPS. + * + * @return bool + */ + protected function is_https_domain(string $domain) + { + $domain = trim($domain, '. '); + $segments = array_reverse(explode('.', $domain)); + $node = &$this->https_domains; + foreach ($segments as $segment) {//Explore the tree + if (isset($node[$segment])) { + $node = &$node[$segment]; + } else { + break; + } + } + return $node === true; + } + + /** + * Force HTTPS for selected Web sites. + * + * @return string + */ + public function https_url(string $url) + { + return ( + strtolower(substr($url, 0, 7)) === 'http://' + && ($parsed = parse_url($url, PHP_URL_HOST)) !== false // Malformed URL + && $parsed !== null // Missing host + && $this->is_https_domain($parsed) // Should be forced? + ) ? substr_replace($url, 's', 4, 0) // Add the 's' to HTTPS + : $url; + } + + /** + * @param int-mask-of<SimplePie::CONSTRUCT_*> $type + * @param string $base + * @return string Sanitized data; false if output encoding is changed to something other than UTF-8 and conversion fails + */ + public function sanitize(string $data, int $type, string $base = '') + { + $data = trim($data); + if ($data !== '' || $type & \SimplePie\SimplePie::CONSTRUCT_IRI) { + if ($type & \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML) { + if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . \SimplePie\SimplePie::PCRE_HTML_ATTRIBUTE . '>)/', $data)) { + $type |= \SimplePie\SimplePie::CONSTRUCT_HTML; + } else { + $type |= \SimplePie\SimplePie::CONSTRUCT_TEXT; + } + } + + if ($type & \SimplePie\SimplePie::CONSTRUCT_BASE64) { + $data = base64_decode($data); + } + + if ($type & (\SimplePie\SimplePie::CONSTRUCT_HTML | \SimplePie\SimplePie::CONSTRUCT_XHTML)) { + if (!class_exists('DOMDocument')) { + throw new \SimplePie\Exception('DOMDocument not found, unable to use sanitizer'); + } + $document = new \DOMDocument(); + $document->encoding = 'UTF-8'; + + // PHPStan seems to have trouble resolving int-mask because bitwise + // operators are used when operators are used when passing this parameter. + // https://github.com/phpstan/phpstan/issues/9384 + /** @var int-mask-of<SimplePie::CONSTRUCT_*> $type */ + $data = $this->preprocess($data, $type); + + set_error_handler([Misc::class, 'silence_errors']); + $document->loadHTML($data); + restore_error_handler(); + + $xpath = new \DOMXPath($document); + + // Strip comments + if ($this->strip_comments) { + /** @var \DOMNodeList<\DOMComment> */ + $comments = $xpath->query('//comment()'); + + foreach ($comments as $comment) { + $parentNode = $comment->parentNode; + assert($parentNode !== null, 'For PHPStan, comment must have a parent'); + $parentNode->removeChild($comment); + } + } + + // Strip out HTML tags and attributes that might cause various security problems. + // Based on recommendations by Mark Pilgrim at: + // https://web.archive.org/web/20110902041826/http://diveintomark.org:80/archives/2003/06/12/how_to_consume_rss_safely + if ($this->strip_htmltags) { + foreach ($this->strip_htmltags as $tag) { + $this->strip_tag($tag, $document, $xpath, $type); + } + } + + if ($this->rename_attributes) { + foreach ($this->rename_attributes as $attrib) { + $this->rename_attr($attrib, $xpath); + } + } + + if ($this->strip_attributes) { + foreach ($this->strip_attributes as $attrib) { + $this->strip_attr($attrib, $xpath); + } + } + + if ($this->add_attributes) { + foreach ($this->add_attributes as $tag => $valuePairs) { + $this->add_attr($tag, $valuePairs, $document); + } + } + + // Replace relative URLs + $this->base = $base; + foreach ($this->replace_url_attributes as $element => $attributes) { + $this->replace_urls($document, $element, $attributes); + } + + // If image handling (caching, etc.) is enabled, cache and rewrite all the image tags. + if ($this->image_handler !== '' && $this->enable_cache) { + $images = $document->getElementsByTagName('img'); + + foreach ($images as $img) { + if ($img->hasAttribute('src')) { + $image_url = $this->cache_namefilter->filter($img->getAttribute('src')); + $cache = $this->get_cache($image_url); + + if ($cache->get_data($image_url, false)) { + $img->setAttribute('src', $this->image_handler . $image_url); + } else { + try { + $file = $this->get_http_client()->request( + Client::METHOD_GET, + $img->getAttribute('src'), + ['X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']] + ); + } catch (ClientException $th) { + continue; + } + + if ((!Misc::is_remote_uri($file->get_final_requested_uri()) || ($file->get_status_code() === 200 || $file->get_status_code() > 206 && $file->get_status_code() < 300))) { + if ($cache->set_data($image_url, ['headers' => $file->get_headers(), 'body' => $file->get_body_content()], $this->cache_duration)) { + $img->setAttribute('src', $this->image_handler . $image_url); + } else { + trigger_error("$this->cache_location is not writable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); + } + } + } + } + } + } + + // Get content node + $div = null; + if (($item = $document->getElementsByTagName('body')->item(0)) !== null) { + $div = $item->firstChild; + } + // Finally, convert to a HTML string + $data = trim((string) $document->saveHTML($div)); + + if ($this->remove_div) { + $data = preg_replace('/^<div' . \SimplePie\SimplePie::PCRE_XML_ATTRIBUTE . '>/', '', $data); + // Cast for PHPStan, it is unable to validate a non-literal regex above. + $data = preg_replace('/<\/div>$/', '', (string) $data); + } else { + $data = preg_replace('/^<div' . \SimplePie\SimplePie::PCRE_XML_ATTRIBUTE . '>/', '<div>', $data); + } + + // Cast for PHPStan, it is unable to validate a non-literal regex above. + $data = str_replace('</source>', '', (string) $data); + } + + if ($type & \SimplePie\SimplePie::CONSTRUCT_IRI) { + $absolute = $this->registry->call(Misc::class, 'absolutize_url', [$data, $base]); + if ($absolute !== false) { + $data = $absolute; + } + } + + if ($type & (\SimplePie\SimplePie::CONSTRUCT_TEXT | \SimplePie\SimplePie::CONSTRUCT_IRI)) { + $data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8'); + } + + if ($this->output_encoding !== 'UTF-8') { + // This really returns string|false but changing encoding is uncommon and we are going to deprecate it, so let’s just lie to PHPStan in the interest of cleaner annotations. + /** @var string */ + $data = $this->registry->call(Misc::class, 'change_encoding', [$data, 'UTF-8', $this->output_encoding]); + } + } + return $data; + } + + /** + * @param int-mask-of<SimplePie::CONSTRUCT_*> $type + * @return string + */ + protected function preprocess(string $html, int $type) + { + $ret = ''; + $html = preg_replace('%</?(?:html|body)[^>]*?'.'>%is', '', $html); + if ($type & ~\SimplePie\SimplePie::CONSTRUCT_XHTML) { + // Atom XHTML constructs are wrapped with a div by default + // Note: No protection if $html contains a stray </div>! + $html = '<div>' . $html . '</div>'; + $ret .= '<!DOCTYPE html>'; + $content_type = 'text/html'; + } else { + $ret .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; + $content_type = 'application/xhtml+xml'; + } + + $ret .= '<html><head>'; + $ret .= '<meta http-equiv="Content-Type" content="' . $content_type . '; charset=utf-8" />'; + $ret .= '</head><body>' . $html . '</body></html>'; + return $ret; + } + + /** + * @param array<string>|string $attributes + * @return void + */ + public function replace_urls(DOMDocument $document, string $tag, $attributes) + { + if (!is_array($attributes)) { + $attributes = [$attributes]; + } + + if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags)) { + $elements = $document->getElementsByTagName($tag); + foreach ($elements as $element) { + foreach ($attributes as $attribute) { + if ($element->hasAttribute($attribute)) { + $value = $this->registry->call(Misc::class, 'absolutize_url', [$element->getAttribute($attribute), $this->base]); + if ($value !== false) { + $value = $this->https_url($value); + $element->setAttribute($attribute, $value); + } + } + } + } + } + } + + /** + * @param array<int, string> $match + * @return string + */ + public function do_strip_htmltags(array $match) + { + if ($this->encode_instead_of_strip) { + if (isset($match[4]) && !in_array(strtolower($match[1]), ['script', 'style'])) { + $match[1] = htmlspecialchars($match[1], ENT_COMPAT, 'UTF-8'); + $match[2] = htmlspecialchars($match[2], ENT_COMPAT, 'UTF-8'); + return "<$match[1]$match[2]>$match[3]</$match[1]>"; + } else { + return htmlspecialchars($match[0], ENT_COMPAT, 'UTF-8'); + } + } elseif (isset($match[4]) && !in_array(strtolower($match[1]), ['script', 'style'])) { + return $match[4]; + } else { + return ''; + } + } + + /** + * @param int-mask-of<SimplePie::CONSTRUCT_*> $type + * @return void + */ + protected function strip_tag(string $tag, DOMDocument $document, DOMXPath $xpath, int $type) + { + $elements = $xpath->query('body//' . $tag); + + if ($elements === false) { + throw new \SimplePie\Exception(sprintf( + '%s(): Possibly malformed expression, check argument #1 ($tag)', + __METHOD__ + ), 1); + } + + if ($this->encode_instead_of_strip) { + foreach ($elements as $element) { + $fragment = $document->createDocumentFragment(); + + // For elements which aren't script or style, include the tag itself + if (!in_array($tag, ['script', 'style'])) { + $text = '<' . $tag; + if ($element->attributes !== null) { + $attrs = []; + foreach ($element->attributes as $name => $attr) { + $value = $attr->value; + + // In XHTML, empty values should never exist, so we repeat the value + if (empty($value) && ($type & \SimplePie\SimplePie::CONSTRUCT_XHTML)) { + $value = $name; + } + // For HTML, empty is fine + elseif (empty($value) && ($type & \SimplePie\SimplePie::CONSTRUCT_HTML)) { + $attrs[] = $name; + continue; + } + + // Standard attribute text + $attrs[] = $name . '="' . $attr->value . '"'; + } + $text .= ' ' . implode(' ', $attrs); + } + $text .= '>'; + $fragment->appendChild(new \DOMText($text)); + } + + $number = $element->childNodes->length; + for ($i = $number; $i > 0; $i--) { + if (($child = $element->childNodes->item(0)) !== null) { + $fragment->appendChild($child); + } + } + + if (!in_array($tag, ['script', 'style'])) { + $fragment->appendChild(new \DOMText('</' . $tag . '>')); + } + + if (($parentNode = $element->parentNode) !== null) { + $parentNode->replaceChild($fragment, $element); + } + } + + return; + } elseif (in_array($tag, ['script', 'style'])) { + foreach ($elements as $element) { + if (($parentNode = $element->parentNode) !== null) { + $parentNode->removeChild($element); + } + } + + return; + } else { + foreach ($elements as $element) { + $fragment = $document->createDocumentFragment(); + $number = $element->childNodes->length; + for ($i = $number; $i > 0; $i--) { + if (($child = $element->childNodes->item(0)) !== null) { + $fragment->appendChild($child); + } + } + + if (($parentNode = $element->parentNode) !== null) { + $parentNode->replaceChild($fragment, $element); + } + } + } + } + + /** + * @return void + */ + protected function strip_attr(string $attrib, DOMXPath $xpath) + { + $elements = $xpath->query('//*[@' . $attrib . ']'); + + if ($elements === false) { + throw new \SimplePie\Exception(sprintf( + '%s(): Possibly malformed expression, check argument #1 ($attrib)', + __METHOD__ + ), 1); + } + + /** @var \DOMElement $element */ + foreach ($elements as $element) { + $element->removeAttribute($attrib); + } + } + + /** + * @return void + */ + protected function rename_attr(string $attrib, DOMXPath $xpath) + { + $elements = $xpath->query('//*[@' . $attrib . ']'); + + if ($elements === false) { + throw new \SimplePie\Exception(sprintf( + '%s(): Possibly malformed expression, check argument #1 ($attrib)', + __METHOD__ + ), 1); + } + + /** @var \DOMElement $element */ + foreach ($elements as $element) { + $element->setAttribute('data-sanitized-' . $attrib, $element->getAttribute($attrib)); + $element->removeAttribute($attrib); + } + } + + /** + * @param array<string, string> $valuePairs + * @return void + */ + protected function add_attr(string $tag, array $valuePairs, DOMDocument $document) + { + $elements = $document->getElementsByTagName($tag); + /** @var \DOMElement $element */ + foreach ($elements as $element) { + foreach ($valuePairs as $attrib => $value) { + $element->setAttribute($attrib, $value); + } + } + } + + /** + * Get a DataCache + * + * @param string $image_url Only needed for BC, can be removed in SimplePie 2.0.0 + * + * @return DataCache + */ + private function get_cache(string $image_url = ''): DataCache + { + if ($this->cache === null) { + // @trigger_error(sprintf('Not providing as PSR-16 cache implementation is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache()".'), \E_USER_DEPRECATED); + $cache = $this->registry->call(Cache::class, 'get_handler', [ + $this->cache_location, + $image_url, + Base::TYPE_IMAGE + ]); + + return new BaseDataCache($cache); + } + + return $this->cache; + } + + /** + * Get a HTTP client + */ + private function get_http_client(): Client + { + if ($this->http_client === null) { + $this->http_client = new FileClient( + $this->registry, + [ + 'timeout' => $this->timeout, + 'redirects' => 5, + 'useragent' => $this->useragent, + 'force_fsockopen' => $this->force_fsockopen, + 'curl_options' => $this->curl_options, + ] + ); + } + + return $this->http_client; + } +} + +class_alias('SimplePie\Sanitize', 'SimplePie_Sanitize'); + +} + +namespace SimplePie { + +/** + * Handles `<atom:source>` + * + * Used by {@see \SimplePie\Item::get_source()} + * + * This class can be overloaded with {@see \SimplePie::set_source_class()} + */ +class Source implements RegistryAware +{ + /** @var Item */ + public $item; + /** @var array<string, mixed> */ + public $data = []; + /** @var Registry */ + protected $registry; + + /** + * @param array<string, mixed> $data + */ + public function __construct(Item $item, array $data) + { + $this->item = $item; + $this->data = $data; + } + + /** + * @return void + */ + public function set_registry(\SimplePie\Registry $registry) + { + $this->registry = $registry; + } + + /** + * @return string + */ + public function __toString() + { + return md5(serialize($this->data)); + } + + /** + * @param string $namespace + * @param string $tag + * @return array<array<string, mixed>>|null + */ + public function get_source_tags(string $namespace, string $tag) + { + if (isset($this->data['child'][$namespace][$tag])) { + return $this->data['child'][$namespace][$tag]; + } + + return null; + } + + /** + * @param array<string, mixed> $element + * @return string + */ + public function get_base(array $element = []) + { + return $this->item->get_base($element); + } + + /** + * @param string $data + * @param int-mask-of<SimplePie::CONSTRUCT_*> $type + * @param string $base + * @return string + */ + public function sanitize(string $data, $type, string $base = '') + { + return $this->item->sanitize($data, $type, $base); + } + + /** + * @return Item + */ + public function get_item() + { + return $this->item; + } + + /** + * @return string|null + */ + public function get_title() + { + if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'title')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'title')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'title')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'title')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'title')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'title')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'title')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + + return null; + } + + /** + * @param int $key + * @return Category|null + */ + public function get_category(int $key = 0) + { + $categories = $this->get_categories(); + if (isset($categories[$key])) { + return $categories[$key]; + } + + return null; + } + + /** + * @return array<Category>|null + */ + public function get_categories() + { + $categories = []; + + foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'category') as $category) { + $term = null; + $scheme = null; + $label = null; + if (isset($category['attribs']['']['term'])) { + $term = $this->sanitize($category['attribs']['']['term'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) { + $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['label'])) { + $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]); + } + foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'category') as $category) { + // This is really the label, but keep this as the term also for BC. + // Label will also work on retrieving because that falls back to term. + $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + if (isset($category['attribs']['']['domain'])) { + $scheme = $this->sanitize($category['attribs']['']['domain'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } else { + $scheme = null; + } + $categories[] = $this->registry->create(Category::class, [$term, $scheme, null]); + } + foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'subject') as $category) { + $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); + } + foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'subject') as $category) { + $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); + } + + if (!empty($categories)) { + return array_unique($categories); + } + + return null; + } + + /** + * @param int $key + * @return Author|null + */ + public function get_author(int $key = 0) + { + $authors = $this->get_authors(); + if (isset($authors[$key])) { + return $authors[$key]; + } + + return null; + } + + /** + * @return array<Author>|null + */ + public function get_authors() + { + $authors = []; + foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'author') as $author) { + $name = null; + $uri = null; + $email = null; + if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'])) { + $name = $this->sanitize($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]['data'])) { + $uri = $author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]; + $uri = $this->sanitize($uri['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($uri)); + } + if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'])) { + $email = $this->sanitize($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) { + $authors[] = $this->registry->create(Author::class, [$name, $uri, $email]); + } + } + if ($author = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'author')) { + $name = null; + $url = null; + $email = null; + if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'])) { + $name = $this->sanitize($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]['data'])) { + $url = $author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]; + $url = $this->sanitize($url['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($url)); + } + if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'])) { + $email = $this->sanitize($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) { + $authors[] = $this->registry->create(Author::class, [$name, $url, $email]); + } + } + foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'creator') as $author) { + $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); + } + foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'creator') as $author) { + $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); + } + foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'author') as $author) { + $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); + } + + if (!empty($authors)) { + return array_unique($authors); + } + + return null; + } + + /** + * @param int $key + * @return Author|null + */ + public function get_contributor(int $key = 0) + { + $contributors = $this->get_contributors(); + if (isset($contributors[$key])) { + return $contributors[$key]; + } + + return null; + } + + /** + * @return array<Author>|null + */ + public function get_contributors() + { + $contributors = []; + foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'contributor') as $contributor) { + $name = null; + $uri = null; + $email = null; + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'])) { + $name = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]['data'])) { + $uri = $contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]; + $uri = $this->sanitize($uri['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($uri)); + } + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'])) { + $email = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) { + $contributors[] = $this->registry->create(Author::class, [$name, $uri, $email]); + } + } + foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'contributor') as $contributor) { + $name = null; + $url = null; + $email = null; + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'])) { + $name = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]['data'])) { + $url = $contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]; + $url = $this->sanitize($url['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($url)); + } + if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'])) { + $email = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) { + $contributors[] = $this->registry->create(Author::class, [$name, $url, $email]); + } + } + + if (!empty($contributors)) { + return array_unique($contributors); + } + + return null; + } + + /** + * @param int $key + * @param string $rel + * @return string|null + */ + public function get_link(int $key = 0, string $rel = 'alternate') + { + $links = $this->get_links($rel); + if (isset($links[$key])) { + return $links[$key]; + } + + return null; + } + + /** + * Added for parity between the parent-level and the item/entry-level. + * + * @return string|null + */ + public function get_permalink() + { + return $this->get_link(0); + } + + /** + * @param string $rel + * @return array<string>|null + */ + public function get_links(string $rel = 'alternate') + { + if (!isset($this->data['links'])) { + $this->data['links'] = []; + if ($links = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'link')) { + foreach ($links as $link) { + if (isset($link['attribs']['']['href'])) { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($link)); + } + } + } + if ($links = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'link')) { + foreach ($links as $link) { + if (isset($link['attribs']['']['href'])) { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($link)); + } + } + } + if ($links = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'link')) { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'link')) { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'link')) { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($links[0])); + } + + $keys = array_keys($this->data['links']); + foreach ($keys as $key) { + $key = (string) $key; + + if ($this->registry->call(Misc::class, 'is_isegment_nz_nc', [$key])) { + if (isset($this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key])) { + $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key]); + $this->data['links'][$key] = &$this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key]; + } else { + $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key] = &$this->data['links'][$key]; + } + } elseif (substr($key, 0, 41) === \SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY) { + $this->data['links'][substr($key, 41)] = &$this->data['links'][$key]; + } + $this->data['links'][$key] = array_unique($this->data['links'][$key]); + } + } + + if (isset($this->data['links'][$rel])) { + return $this->data['links'][$rel]; + } + + return null; + } + + /** + * @return string|null + */ + public function get_description() + { + if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'subtitle')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'tagline')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'description')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'description')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'description')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'description')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'description')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'summary')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'subtitle')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($return[0])); + } + + return null; + } + + /** + * @return string|null + */ + public function get_copyright() + { + if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'rights')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'copyright')) { + return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'copyright')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'rights')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'rights')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + + return null; + } + + /** + * @return string|null + */ + public function get_language() + { + if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'language')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'language')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'language')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } elseif (isset($this->data['xml_lang'])) { + return $this->sanitize($this->data['xml_lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); + } + + return null; + } + + /** + * @return float|null + */ + public function get_latitude() + { + if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'lat')) { + return (float) $return[0]['data']; + } elseif (($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { + return (float) $match[1]; + } + + return null; + } + + /** + * @return float|null + */ + public function get_longitude() + { + if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'long')) { + return (float) $return[0]['data']; + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'lon')) { + return (float) $return[0]['data']; + } elseif (($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { + return (float) $match[2]; + } + + return null; + } + + /** + * @return string|null + */ + public function get_image_url() + { + if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'image')) { + return $this->sanitize($return[0]['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'logo')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($return[0])); + } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'icon')) { + return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($return[0])); + } + + return null; + } +} + +class_alias('SimplePie\Source', 'SimplePie_Source'); + +} + +namespace SimplePie\XML\Declaration { + +/** + * Parses the XML Declaration + */ +class Parser +{ + /** + * XML Version + * + * @access public + * @var string + */ + public $version = '1.0'; + + /** + * Encoding + * + * @access public + * @var string + */ + public $encoding = 'UTF-8'; + + /** + * Standalone + * + * @access public + * @var bool + */ + public $standalone = false; + + private const STATE_BEFORE_VERSION_NAME = 'before_version_name'; + + private const STATE_VERSION_NAME = 'version_name'; + + private const STATE_VERSION_EQUALS = 'version_equals'; + + private const STATE_VERSION_VALUE = 'version_value'; + + private const STATE_ENCODING_NAME = 'encoding_name'; + + private const STATE_EMIT = 'emit'; + + private const STATE_ENCODING_EQUALS = 'encoding_equals'; + + private const STATE_STANDALONE_NAME = 'standalone_name'; + + private const STATE_ENCODING_VALUE = 'encoding_value'; + + private const STATE_STANDALONE_EQUALS = 'standalone_equals'; + + private const STATE_STANDALONE_VALUE = 'standalone_value'; + + private const STATE_ERROR = false; + + /** + * Current state of the state machine + * + * @access private + * @var self::STATE_* + */ + public $state = self::STATE_BEFORE_VERSION_NAME; + + /** + * Input data + * + * @access private + * @var string + */ + public $data = ''; + + /** + * Input data length (to avoid calling strlen() everytime this is needed) + * + * @access private + * @var int + */ + public $data_length = 0; + + /** + * Current position of the pointer + * + * @var int + * @access private + */ + public $position = 0; + + /** + * Create an instance of the class with the input data + * + * @access public + * @param string $data Input data + */ + public function __construct(string $data) + { + $this->data = $data; + $this->data_length = strlen($this->data); + } + + /** + * Parse the input data + * + * @access public + * @return bool true on success, false on failure + */ + public function parse(): bool + { + while ($this->state && $this->state !== self::STATE_EMIT && $this->has_data()) { + $state = $this->state; + $this->$state(); + } + $this->data = ''; + if ($this->state === self::STATE_EMIT) { + return true; + } + + // Reset the parser state. + $this->version = '1.0'; + $this->encoding = 'UTF-8'; + $this->standalone = false; + return false; + } + + /** + * Check whether there is data beyond the pointer + * + * @access private + * @return bool true if there is further data, false if not + */ + public function has_data(): bool + { + return (bool) ($this->position < $this->data_length); + } + + /** + * Advance past any whitespace + * + * @return int Number of whitespace characters passed + */ + public function skip_whitespace() + { + $whitespace = strspn($this->data, "\x09\x0A\x0D\x20", $this->position); + $this->position += $whitespace; + return $whitespace; + } + + /** + * Read value + * + * @return string|false + */ + public function get_value() + { + $quote = substr($this->data, $this->position, 1); + if ($quote === '"' || $quote === "'") { + $this->position++; + $len = strcspn($this->data, $quote, $this->position); + if ($this->has_data()) { + $value = substr($this->data, $this->position, $len); + $this->position += $len + 1; + return $value; + } + } + return false; + } + + public function before_version_name(): void + { + if ($this->skip_whitespace()) { + $this->state = self::STATE_VERSION_NAME; + } else { + $this->state = self::STATE_ERROR; + } + } + + public function version_name(): void + { + if (substr($this->data, $this->position, 7) === 'version') { + $this->position += 7; + $this->skip_whitespace(); + $this->state = self::STATE_VERSION_EQUALS; + } else { + $this->state = self::STATE_ERROR; + } + } + + public function version_equals(): void + { + if (substr($this->data, $this->position, 1) === '=') { + $this->position++; + $this->skip_whitespace(); + $this->state = self::STATE_VERSION_VALUE; + } else { + $this->state = self::STATE_ERROR; + } + } + + public function version_value(): void + { + if ($version = $this->get_value()) { + $this->version = $version; + $this->skip_whitespace(); + if ($this->has_data()) { + $this->state = self::STATE_ENCODING_NAME; + } else { + $this->state = self::STATE_EMIT; + } + } else { + $this->state = self::STATE_ERROR; + } + } + + public function encoding_name(): void + { + if (substr($this->data, $this->position, 8) === 'encoding') { + $this->position += 8; + $this->skip_whitespace(); + $this->state = self::STATE_ENCODING_EQUALS; + } else { + $this->state = self::STATE_STANDALONE_NAME; + } + } + + public function encoding_equals(): void + { + if (substr($this->data, $this->position, 1) === '=') { + $this->position++; + $this->skip_whitespace(); + $this->state = self::STATE_ENCODING_VALUE; + } else { + $this->state = self::STATE_ERROR; + } + } + + public function encoding_value(): void + { + if ($encoding = $this->get_value()) { + $this->encoding = strtoupper($encoding); + $this->skip_whitespace(); + if ($this->has_data()) { + $this->state = self::STATE_STANDALONE_NAME; + } else { + $this->state = self::STATE_EMIT; + } + } else { + $this->state = self::STATE_ERROR; + } + } + + public function standalone_name(): void + { + if (substr($this->data, $this->position, 10) === 'standalone') { + $this->position += 10; + $this->skip_whitespace(); + $this->state = self::STATE_STANDALONE_EQUALS; + } else { + $this->state = self::STATE_ERROR; + } + } + + public function standalone_equals(): void + { + if (substr($this->data, $this->position, 1) === '=') { + $this->position++; + $this->skip_whitespace(); + $this->state = self::STATE_STANDALONE_VALUE; + } else { + $this->state = self::STATE_ERROR; + } + } + + public function standalone_value(): void + { + if ($standalone = $this->get_value()) { + switch ($standalone) { + case 'yes': + $this->standalone = true; + break; + + case 'no': + $this->standalone = false; + break; + + default: + $this->state = self::STATE_ERROR; + return; + } + + $this->skip_whitespace(); + if ($this->has_data()) { + $this->state = self::STATE_ERROR; + } else { + $this->state = self::STATE_EMIT; + } + } else { + $this->state = self::STATE_ERROR; + } + } +} + +class_alias('SimplePie\XML\Declaration\Parser', 'SimplePie_XML_Declaration_Parser'); + +} + +namespace { + +use SimplePie\Misc; + +class_exists('SimplePie\Misc'); + +// @trigger_error(sprintf('Using the "SimplePie_Misc" class is deprecated since SimplePie 1.7.0, use "SimplePie\Misc" instead.'), \E_USER_DEPRECATED); + +/** @phpstan-ignore-next-line */ +if (\false) { + /** @deprecated since SimplePie 1.7.0, use "SimplePie\Misc" instead */ + class SimplePie_Misc extends Misc + { + } +} + +} + +namespace { + +/** + * SimplePie class. + * + * Class for backward compatibility. + * + * @deprecated Use {@see SimplePie} directly + */ +class SimplePie_Core extends SimplePie +{ +} + +} + +namespace { + +/** + * Decode HTML Entities + * + * This implements HTML5 as of revision 967 (2007-06-28) + * + * @deprecated Use DOMDocument instead! + */ +class SimplePie_Decode_HTML_Entities +{ + /** + * Data to be parsed + * + * @access private + * @var string + */ + public $data = ''; + + /** + * Currently consumed bytes + * + * @access private + * @var string + */ + public $consumed = ''; + + /** + * Position of the current byte being parsed + * + * @access private + * @var int + */ + public $position = 0; + + /** + * Create an instance of the class with the input data + * + * @access public + * @param string $data Input data + */ + public function __construct(string $data) + { + $this->data = $data; + } + + /** + * Parse the input data + * + * @access public + * @return string Output data + */ + public function parse() + { + while (($position = strpos($this->data, '&', $this->position)) !== false) { + $this->position = $position; + $this->consume(); + $this->entity(); + $this->consumed = ''; + } + return $this->data; + } + + /** + * Consume the next byte + * + * @access private + * @return string|false The next byte, or false, if there is no more data + */ + public function consume() + { + if (isset($this->data[$this->position])) { + $this->consumed .= $this->data[$this->position]; + return $this->data[$this->position++]; + } + + return false; + } + + /** + * Consume a range of characters + * + * @access private + * @param string $chars Characters to consume + * @return string|false A series of characters that match the range, or false + */ + public function consume_range(string $chars) + { + if ($len = strspn($this->data, $chars, $this->position)) { + $data = substr($this->data, $this->position, $len); + $this->consumed .= $data; + $this->position += $len; + return $data; + } + + return false; + } + + /** + * Unconsume one byte + * + * @access private + * @return void + */ + public function unconsume() + { + $this->consumed = substr($this->consumed, 0, -1); + $this->position--; + } + + /** + * Decode an entity + * + * @access private + * @return void + */ + public function entity() + { + switch ($this->consume()) { + case "\x09": + case "\x0A": + case "\x0B": + case "\x0C": + case "\x20": + case "\x3C": + case "\x26": + case false: + break; + + case "\x23": + switch ($this->consume()) { + case "\x78": + case "\x58": + $range = '0123456789ABCDEFabcdef'; + $hex = true; + break; + + default: + $range = '0123456789'; + $hex = false; + $this->unconsume(); + break; + } + + if ($codepoint = $this->consume_range($range)) { + static $windows_1252_specials = [0x0D => "\x0A", 0x80 => "\xE2\x82\xAC", 0x81 => "\xEF\xBF\xBD", 0x82 => "\xE2\x80\x9A", 0x83 => "\xC6\x92", 0x84 => "\xE2\x80\x9E", 0x85 => "\xE2\x80\xA6", 0x86 => "\xE2\x80\xA0", 0x87 => "\xE2\x80\xA1", 0x88 => "\xCB\x86", 0x89 => "\xE2\x80\xB0", 0x8A => "\xC5\xA0", 0x8B => "\xE2\x80\xB9", 0x8C => "\xC5\x92", 0x8D => "\xEF\xBF\xBD", 0x8E => "\xC5\xBD", 0x8F => "\xEF\xBF\xBD", 0x90 => "\xEF\xBF\xBD", 0x91 => "\xE2\x80\x98", 0x92 => "\xE2\x80\x99", 0x93 => "\xE2\x80\x9C", 0x94 => "\xE2\x80\x9D", 0x95 => "\xE2\x80\xA2", 0x96 => "\xE2\x80\x93", 0x97 => "\xE2\x80\x94", 0x98 => "\xCB\x9C", 0x99 => "\xE2\x84\xA2", 0x9A => "\xC5\xA1", 0x9B => "\xE2\x80\xBA", 0x9C => "\xC5\x93", 0x9D => "\xEF\xBF\xBD", 0x9E => "\xC5\xBE", 0x9F => "\xC5\xB8"]; + + if ($hex) { + // Cap to PHP_INT_MAX to ensure consistent behaviour if $codepoint is so large + // it cannot fit into int – just casting float to int might return junk (e.g. a negative number). + // If it is so large, `Misc::codepoint_to_utf8` will just return a replacement character. + $codepoint = (int) min(hexdec($codepoint), \PHP_INT_MAX); + } else { + // Casting string to int caps at PHP_INT_MAX automatically. + $codepoint = (int) $codepoint; + } + + if (isset($windows_1252_specials[$codepoint])) { + $replacement = $windows_1252_specials[$codepoint]; + } else { + $replacement = SimplePie_Misc::codepoint_to_utf8($codepoint); + } + + if (!in_array($this->consume(), [';', false], true)) { + $this->unconsume(); + } + + $consumed_length = strlen($this->consumed); + $this->data = substr_replace($this->data, $replacement, $this->position - $consumed_length, $consumed_length); + $this->position += strlen($replacement) - $consumed_length; + } + break; + + default: + static $entities = [ + 'Aacute' => "\xC3\x81", + 'aacute' => "\xC3\xA1", + 'Aacute;' => "\xC3\x81", + 'aacute;' => "\xC3\xA1", + 'Acirc' => "\xC3\x82", + 'acirc' => "\xC3\xA2", + 'Acirc;' => "\xC3\x82", + 'acirc;' => "\xC3\xA2", + 'acute' => "\xC2\xB4", + 'acute;' => "\xC2\xB4", + 'AElig' => "\xC3\x86", + 'aelig' => "\xC3\xA6", + 'AElig;' => "\xC3\x86", + 'aelig;' => "\xC3\xA6", + 'Agrave' => "\xC3\x80", + 'agrave' => "\xC3\xA0", + 'Agrave;' => "\xC3\x80", + 'agrave;' => "\xC3\xA0", + 'alefsym;' => "\xE2\x84\xB5", + 'Alpha;' => "\xCE\x91", + 'alpha;' => "\xCE\xB1", + 'AMP' => "\x26", + 'amp' => "\x26", + 'AMP;' => "\x26", + 'amp;' => "\x26", + 'and;' => "\xE2\x88\xA7", + 'ang;' => "\xE2\x88\xA0", + 'apos;' => "\x27", + 'Aring' => "\xC3\x85", + 'aring' => "\xC3\xA5", + 'Aring;' => "\xC3\x85", + 'aring;' => "\xC3\xA5", + 'asymp;' => "\xE2\x89\x88", + 'Atilde' => "\xC3\x83", + 'atilde' => "\xC3\xA3", + 'Atilde;' => "\xC3\x83", + 'atilde;' => "\xC3\xA3", + 'Auml' => "\xC3\x84", + 'auml' => "\xC3\xA4", + 'Auml;' => "\xC3\x84", + 'auml;' => "\xC3\xA4", + 'bdquo;' => "\xE2\x80\x9E", + 'Beta;' => "\xCE\x92", + 'beta;' => "\xCE\xB2", + 'brvbar' => "\xC2\xA6", + 'brvbar;' => "\xC2\xA6", + 'bull;' => "\xE2\x80\xA2", + 'cap;' => "\xE2\x88\xA9", + 'Ccedil' => "\xC3\x87", + 'ccedil' => "\xC3\xA7", + 'Ccedil;' => "\xC3\x87", + 'ccedil;' => "\xC3\xA7", + 'cedil' => "\xC2\xB8", + 'cedil;' => "\xC2\xB8", + 'cent' => "\xC2\xA2", + 'cent;' => "\xC2\xA2", + 'Chi;' => "\xCE\xA7", + 'chi;' => "\xCF\x87", + 'circ;' => "\xCB\x86", + 'clubs;' => "\xE2\x99\xA3", + 'cong;' => "\xE2\x89\x85", + 'COPY' => "\xC2\xA9", + 'copy' => "\xC2\xA9", + 'COPY;' => "\xC2\xA9", + 'copy;' => "\xC2\xA9", + 'crarr;' => "\xE2\x86\xB5", + 'cup;' => "\xE2\x88\xAA", + 'curren' => "\xC2\xA4", + 'curren;' => "\xC2\xA4", + 'Dagger;' => "\xE2\x80\xA1", + 'dagger;' => "\xE2\x80\xA0", + 'dArr;' => "\xE2\x87\x93", + 'darr;' => "\xE2\x86\x93", + 'deg' => "\xC2\xB0", + 'deg;' => "\xC2\xB0", + 'Delta;' => "\xCE\x94", + 'delta;' => "\xCE\xB4", + 'diams;' => "\xE2\x99\xA6", + 'divide' => "\xC3\xB7", + 'divide;' => "\xC3\xB7", + 'Eacute' => "\xC3\x89", + 'eacute' => "\xC3\xA9", + 'Eacute;' => "\xC3\x89", + 'eacute;' => "\xC3\xA9", + 'Ecirc' => "\xC3\x8A", + 'ecirc' => "\xC3\xAA", + 'Ecirc;' => "\xC3\x8A", + 'ecirc;' => "\xC3\xAA", + 'Egrave' => "\xC3\x88", + 'egrave' => "\xC3\xA8", + 'Egrave;' => "\xC3\x88", + 'egrave;' => "\xC3\xA8", + 'empty;' => "\xE2\x88\x85", + 'emsp;' => "\xE2\x80\x83", + 'ensp;' => "\xE2\x80\x82", + 'Epsilon;' => "\xCE\x95", + 'epsilon;' => "\xCE\xB5", + 'equiv;' => "\xE2\x89\xA1", + 'Eta;' => "\xCE\x97", + 'eta;' => "\xCE\xB7", + 'ETH' => "\xC3\x90", + 'eth' => "\xC3\xB0", + 'ETH;' => "\xC3\x90", + 'eth;' => "\xC3\xB0", + 'Euml' => "\xC3\x8B", + 'euml' => "\xC3\xAB", + 'Euml;' => "\xC3\x8B", + 'euml;' => "\xC3\xAB", + 'euro;' => "\xE2\x82\xAC", + 'exist;' => "\xE2\x88\x83", + 'fnof;' => "\xC6\x92", + 'forall;' => "\xE2\x88\x80", + 'frac12' => "\xC2\xBD", + 'frac12;' => "\xC2\xBD", + 'frac14' => "\xC2\xBC", + 'frac14;' => "\xC2\xBC", + 'frac34' => "\xC2\xBE", + 'frac34;' => "\xC2\xBE", + 'frasl;' => "\xE2\x81\x84", + 'Gamma;' => "\xCE\x93", + 'gamma;' => "\xCE\xB3", + 'ge;' => "\xE2\x89\xA5", + 'GT' => "\x3E", + 'gt' => "\x3E", + 'GT;' => "\x3E", + 'gt;' => "\x3E", + 'hArr;' => "\xE2\x87\x94", + 'harr;' => "\xE2\x86\x94", + 'hearts;' => "\xE2\x99\xA5", + 'hellip;' => "\xE2\x80\xA6", + 'Iacute' => "\xC3\x8D", + 'iacute' => "\xC3\xAD", + 'Iacute;' => "\xC3\x8D", + 'iacute;' => "\xC3\xAD", + 'Icirc' => "\xC3\x8E", + 'icirc' => "\xC3\xAE", + 'Icirc;' => "\xC3\x8E", + 'icirc;' => "\xC3\xAE", + 'iexcl' => "\xC2\xA1", + 'iexcl;' => "\xC2\xA1", + 'Igrave' => "\xC3\x8C", + 'igrave' => "\xC3\xAC", + 'Igrave;' => "\xC3\x8C", + 'igrave;' => "\xC3\xAC", + 'image;' => "\xE2\x84\x91", + 'infin;' => "\xE2\x88\x9E", + 'int;' => "\xE2\x88\xAB", + 'Iota;' => "\xCE\x99", + 'iota;' => "\xCE\xB9", + 'iquest' => "\xC2\xBF", + 'iquest;' => "\xC2\xBF", + 'isin;' => "\xE2\x88\x88", + 'Iuml' => "\xC3\x8F", + 'iuml' => "\xC3\xAF", + 'Iuml;' => "\xC3\x8F", + 'iuml;' => "\xC3\xAF", + 'Kappa;' => "\xCE\x9A", + 'kappa;' => "\xCE\xBA", + 'Lambda;' => "\xCE\x9B", + 'lambda;' => "\xCE\xBB", + 'lang;' => "\xE3\x80\x88", + 'laquo' => "\xC2\xAB", + 'laquo;' => "\xC2\xAB", + 'lArr;' => "\xE2\x87\x90", + 'larr;' => "\xE2\x86\x90", + 'lceil;' => "\xE2\x8C\x88", + 'ldquo;' => "\xE2\x80\x9C", + 'le;' => "\xE2\x89\xA4", + 'lfloor;' => "\xE2\x8C\x8A", + 'lowast;' => "\xE2\x88\x97", + 'loz;' => "\xE2\x97\x8A", + 'lrm;' => "\xE2\x80\x8E", + 'lsaquo;' => "\xE2\x80\xB9", + 'lsquo;' => "\xE2\x80\x98", + 'LT' => "\x3C", + 'lt' => "\x3C", + 'LT;' => "\x3C", + 'lt;' => "\x3C", + 'macr' => "\xC2\xAF", + 'macr;' => "\xC2\xAF", + 'mdash;' => "\xE2\x80\x94", + 'micro' => "\xC2\xB5", + 'micro;' => "\xC2\xB5", + 'middot' => "\xC2\xB7", + 'middot;' => "\xC2\xB7", + 'minus;' => "\xE2\x88\x92", + 'Mu;' => "\xCE\x9C", + 'mu;' => "\xCE\xBC", + 'nabla;' => "\xE2\x88\x87", + 'nbsp' => "\xC2\xA0", + 'nbsp;' => "\xC2\xA0", + 'ndash;' => "\xE2\x80\x93", + 'ne;' => "\xE2\x89\xA0", + 'ni;' => "\xE2\x88\x8B", + 'not' => "\xC2\xAC", + 'not;' => "\xC2\xAC", + 'notin;' => "\xE2\x88\x89", + 'nsub;' => "\xE2\x8A\x84", + 'Ntilde' => "\xC3\x91", + 'ntilde' => "\xC3\xB1", + 'Ntilde;' => "\xC3\x91", + 'ntilde;' => "\xC3\xB1", + 'Nu;' => "\xCE\x9D", + 'nu;' => "\xCE\xBD", + 'Oacute' => "\xC3\x93", + 'oacute' => "\xC3\xB3", + 'Oacute;' => "\xC3\x93", + 'oacute;' => "\xC3\xB3", + 'Ocirc' => "\xC3\x94", + 'ocirc' => "\xC3\xB4", + 'Ocirc;' => "\xC3\x94", + 'ocirc;' => "\xC3\xB4", + 'OElig;' => "\xC5\x92", + 'oelig;' => "\xC5\x93", + 'Ograve' => "\xC3\x92", + 'ograve' => "\xC3\xB2", + 'Ograve;' => "\xC3\x92", + 'ograve;' => "\xC3\xB2", + 'oline;' => "\xE2\x80\xBE", + 'Omega;' => "\xCE\xA9", + 'omega;' => "\xCF\x89", + 'Omicron;' => "\xCE\x9F", + 'omicron;' => "\xCE\xBF", + 'oplus;' => "\xE2\x8A\x95", + 'or;' => "\xE2\x88\xA8", + 'ordf' => "\xC2\xAA", + 'ordf;' => "\xC2\xAA", + 'ordm' => "\xC2\xBA", + 'ordm;' => "\xC2\xBA", + 'Oslash' => "\xC3\x98", + 'oslash' => "\xC3\xB8", + 'Oslash;' => "\xC3\x98", + 'oslash;' => "\xC3\xB8", + 'Otilde' => "\xC3\x95", + 'otilde' => "\xC3\xB5", + 'Otilde;' => "\xC3\x95", + 'otilde;' => "\xC3\xB5", + 'otimes;' => "\xE2\x8A\x97", + 'Ouml' => "\xC3\x96", + 'ouml' => "\xC3\xB6", + 'Ouml;' => "\xC3\x96", + 'ouml;' => "\xC3\xB6", + 'para' => "\xC2\xB6", + 'para;' => "\xC2\xB6", + 'part;' => "\xE2\x88\x82", + 'permil;' => "\xE2\x80\xB0", + 'perp;' => "\xE2\x8A\xA5", + 'Phi;' => "\xCE\xA6", + 'phi;' => "\xCF\x86", + 'Pi;' => "\xCE\xA0", + 'pi;' => "\xCF\x80", + 'piv;' => "\xCF\x96", + 'plusmn' => "\xC2\xB1", + 'plusmn;' => "\xC2\xB1", + 'pound' => "\xC2\xA3", + 'pound;' => "\xC2\xA3", + 'Prime;' => "\xE2\x80\xB3", + 'prime;' => "\xE2\x80\xB2", + 'prod;' => "\xE2\x88\x8F", + 'prop;' => "\xE2\x88\x9D", + 'Psi;' => "\xCE\xA8", + 'psi;' => "\xCF\x88", + 'QUOT' => "\x22", + 'quot' => "\x22", + 'QUOT;' => "\x22", + 'quot;' => "\x22", + 'radic;' => "\xE2\x88\x9A", + 'rang;' => "\xE3\x80\x89", + 'raquo' => "\xC2\xBB", + 'raquo;' => "\xC2\xBB", + 'rArr;' => "\xE2\x87\x92", + 'rarr;' => "\xE2\x86\x92", + 'rceil;' => "\xE2\x8C\x89", + 'rdquo;' => "\xE2\x80\x9D", + 'real;' => "\xE2\x84\x9C", + 'REG' => "\xC2\xAE", + 'reg' => "\xC2\xAE", + 'REG;' => "\xC2\xAE", + 'reg;' => "\xC2\xAE", + 'rfloor;' => "\xE2\x8C\x8B", + 'Rho;' => "\xCE\xA1", + 'rho;' => "\xCF\x81", + 'rlm;' => "\xE2\x80\x8F", + 'rsaquo;' => "\xE2\x80\xBA", + 'rsquo;' => "\xE2\x80\x99", + 'sbquo;' => "\xE2\x80\x9A", + 'Scaron;' => "\xC5\xA0", + 'scaron;' => "\xC5\xA1", + 'sdot;' => "\xE2\x8B\x85", + 'sect' => "\xC2\xA7", + 'sect;' => "\xC2\xA7", + 'shy' => "\xC2\xAD", + 'shy;' => "\xC2\xAD", + 'Sigma;' => "\xCE\xA3", + 'sigma;' => "\xCF\x83", + 'sigmaf;' => "\xCF\x82", + 'sim;' => "\xE2\x88\xBC", + 'spades;' => "\xE2\x99\xA0", + 'sub;' => "\xE2\x8A\x82", + 'sube;' => "\xE2\x8A\x86", + 'sum;' => "\xE2\x88\x91", + 'sup;' => "\xE2\x8A\x83", + 'sup1' => "\xC2\xB9", + 'sup1;' => "\xC2\xB9", + 'sup2' => "\xC2\xB2", + 'sup2;' => "\xC2\xB2", + 'sup3' => "\xC2\xB3", + 'sup3;' => "\xC2\xB3", + 'supe;' => "\xE2\x8A\x87", + 'szlig' => "\xC3\x9F", + 'szlig;' => "\xC3\x9F", + 'Tau;' => "\xCE\xA4", + 'tau;' => "\xCF\x84", + 'there4;' => "\xE2\x88\xB4", + 'Theta;' => "\xCE\x98", + 'theta;' => "\xCE\xB8", + 'thetasym;' => "\xCF\x91", + 'thinsp;' => "\xE2\x80\x89", + 'THORN' => "\xC3\x9E", + 'thorn' => "\xC3\xBE", + 'THORN;' => "\xC3\x9E", + 'thorn;' => "\xC3\xBE", + 'tilde;' => "\xCB\x9C", + 'times' => "\xC3\x97", + 'times;' => "\xC3\x97", + 'TRADE;' => "\xE2\x84\xA2", + 'trade;' => "\xE2\x84\xA2", + 'Uacute' => "\xC3\x9A", + 'uacute' => "\xC3\xBA", + 'Uacute;' => "\xC3\x9A", + 'uacute;' => "\xC3\xBA", + 'uArr;' => "\xE2\x87\x91", + 'uarr;' => "\xE2\x86\x91", + 'Ucirc' => "\xC3\x9B", + 'ucirc' => "\xC3\xBB", + 'Ucirc;' => "\xC3\x9B", + 'ucirc;' => "\xC3\xBB", + 'Ugrave' => "\xC3\x99", + 'ugrave' => "\xC3\xB9", + 'Ugrave;' => "\xC3\x99", + 'ugrave;' => "\xC3\xB9", + 'uml' => "\xC2\xA8", + 'uml;' => "\xC2\xA8", + 'upsih;' => "\xCF\x92", + 'Upsilon;' => "\xCE\xA5", + 'upsilon;' => "\xCF\x85", + 'Uuml' => "\xC3\x9C", + 'uuml' => "\xC3\xBC", + 'Uuml;' => "\xC3\x9C", + 'uuml;' => "\xC3\xBC", + 'weierp;' => "\xE2\x84\x98", + 'Xi;' => "\xCE\x9E", + 'xi;' => "\xCE\xBE", + 'Yacute' => "\xC3\x9D", + 'yacute' => "\xC3\xBD", + 'Yacute;' => "\xC3\x9D", + 'yacute;' => "\xC3\xBD", + 'yen' => "\xC2\xA5", + 'yen;' => "\xC2\xA5", + 'yuml' => "\xC3\xBF", + 'Yuml;' => "\xC5\xB8", + 'yuml;' => "\xC3\xBF", + 'Zeta;' => "\xCE\x96", + 'zeta;' => "\xCE\xB6", + 'zwj;' => "\xE2\x80\x8D", + 'zwnj;' => "\xE2\x80\x8C" + ]; + + for ($i = 0, $match = null; $i < 9 && $this->consume() !== false; $i++) { + // Cast for PHPStan on PHP < 8.0: We consumed as per the loop condition, + // so `$this->consumed` is non-empty and the substr offset is valid. + $consumed = (string) substr($this->consumed, 1); + if (isset($entities[$consumed])) { + $match = $consumed; + } + } + + if ($match !== null) { + $this->data = substr_replace($this->data, $entities[$match], $this->position - strlen($consumed) - 1, strlen($match) + 1); + $this->position += strlen($entities[$match]) - strlen($consumed) - 1; + } + break; + } + } +} + +} + +namespace { + +use SimplePie\SimplePie as NamespacedSimplePie; + +class_exists('SimplePie\SimplePie'); + +// @trigger_error(sprintf('Using the "SimplePie" class is deprecated since SimplePie 1.7.0, use "SimplePie\SimplePie" instead.'), \E_USER_DEPRECATED); + +/** @phpstan-ignore-next-line */ +if (\false) { + /** @deprecated since SimplePie 1.7.0, use "SimplePie\SimplePie" instead */ + class SimplePie extends NamespacedSimplePie + { + } +} + +/** + * SimplePie Name + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAME instead. + */ +define('SIMPLEPIE_NAME', NamespacedSimplePie::NAME); + +/** + * SimplePie Version + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::VERSION instead. + */ +define('SIMPLEPIE_VERSION', NamespacedSimplePie::VERSION); + +/** + * SimplePie Build + * @todo Hardcode for release (there's no need to have to call SimplePie_Misc::get_build() only every load of simplepie.inc) + * @deprecated since SimplePie 1.7.0, use \SimplePie\Misc::get_build() instead. + */ +define('SIMPLEPIE_BUILD', '20250930132948'); + +/** + * SimplePie Website URL + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::URL instead. + */ +define('SIMPLEPIE_URL', NamespacedSimplePie::URL); + +/** + * SimplePie Useragent + * @see SimplePie::set_useragent() + * @deprecated since SimplePie 1.7.0, use \SimplePie\Misc::get_default_useragent() instead. + */ +define('SIMPLEPIE_USERAGENT', \SimplePie\Misc::get_default_useragent()); + +/** + * SimplePie Linkback + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::LINKBACK instead. + */ +define('SIMPLEPIE_LINKBACK', NamespacedSimplePie::LINKBACK); + +/** + * No Autodiscovery + * @see SimplePie::set_autodiscovery_level() + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::LOCATOR_NONE instead. + */ +define('SIMPLEPIE_LOCATOR_NONE', NamespacedSimplePie::LOCATOR_NONE); + +/** + * Feed Link Element Autodiscovery + * @see SimplePie::set_autodiscovery_level() + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::LOCATOR_AUTODISCOVERY instead. + */ +define('SIMPLEPIE_LOCATOR_AUTODISCOVERY', NamespacedSimplePie::LOCATOR_AUTODISCOVERY); + +/** + * Local Feed Extension Autodiscovery + * @see SimplePie::set_autodiscovery_level() + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::LOCATOR_LOCAL_EXTENSION instead. + */ +define('SIMPLEPIE_LOCATOR_LOCAL_EXTENSION', NamespacedSimplePie::LOCATOR_LOCAL_EXTENSION); + +/** + * Local Feed Body Autodiscovery + * @see SimplePie::set_autodiscovery_level() + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::LOCATOR_LOCAL_BODY instead. + */ +define('SIMPLEPIE_LOCATOR_LOCAL_BODY', NamespacedSimplePie::LOCATOR_LOCAL_BODY); + +/** + * Remote Feed Extension Autodiscovery + * @see SimplePie::set_autodiscovery_level() + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::LOCATOR_REMOTE_EXTENSION instead. + */ +define('SIMPLEPIE_LOCATOR_REMOTE_EXTENSION', NamespacedSimplePie::LOCATOR_REMOTE_EXTENSION); + +/** + * Remote Feed Body Autodiscovery + * @see SimplePie::set_autodiscovery_level() + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::LOCATOR_REMOTE_BODY instead. + */ +define('SIMPLEPIE_LOCATOR_REMOTE_BODY', NamespacedSimplePie::LOCATOR_REMOTE_BODY); + +/** + * All Feed Autodiscovery + * @see SimplePie::set_autodiscovery_level() + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::LOCATOR_ALL instead. + */ +define('SIMPLEPIE_LOCATOR_ALL', NamespacedSimplePie::LOCATOR_ALL); + +/** + * No known feed type + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_NONE instead. + */ +define('SIMPLEPIE_TYPE_NONE', NamespacedSimplePie::TYPE_NONE); + +/** + * RSS 0.90 + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_090 instead. + */ +define('SIMPLEPIE_TYPE_RSS_090', NamespacedSimplePie::TYPE_RSS_090); + +/** + * RSS 0.91 (Netscape) + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_091_NETSCAPE instead. + */ +define('SIMPLEPIE_TYPE_RSS_091_NETSCAPE', NamespacedSimplePie::TYPE_RSS_091_NETSCAPE); + +/** + * RSS 0.91 (Userland) + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_091_USERLAND instead. + */ +define('SIMPLEPIE_TYPE_RSS_091_USERLAND', NamespacedSimplePie::TYPE_RSS_091_USERLAND); + +/** + * RSS 0.91 (both Netscape and Userland) + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_091 instead. + */ +define('SIMPLEPIE_TYPE_RSS_091', NamespacedSimplePie::TYPE_RSS_091); + +/** + * RSS 0.92 + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_092 instead. + */ +define('SIMPLEPIE_TYPE_RSS_092', NamespacedSimplePie::TYPE_RSS_092); + +/** + * RSS 0.93 + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_093 instead. + */ +define('SIMPLEPIE_TYPE_RSS_093', NamespacedSimplePie::TYPE_RSS_093); + +/** + * RSS 0.94 + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_094 instead. + */ +define('SIMPLEPIE_TYPE_RSS_094', NamespacedSimplePie::TYPE_RSS_094); + +/** + * RSS 1.0 + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_10 instead. + */ +define('SIMPLEPIE_TYPE_RSS_10', NamespacedSimplePie::TYPE_RSS_10); + +/** + * RSS 2.0 + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_20 instead. + */ +define('SIMPLEPIE_TYPE_RSS_20', NamespacedSimplePie::TYPE_RSS_20); + +/** + * RDF-based RSS + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_RDF instead. + */ +define('SIMPLEPIE_TYPE_RSS_RDF', NamespacedSimplePie::TYPE_RSS_RDF); + +/** + * Non-RDF-based RSS (truly intended as syndication format) + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_SYNDICATION instead. + */ +define('SIMPLEPIE_TYPE_RSS_SYNDICATION', NamespacedSimplePie::TYPE_RSS_SYNDICATION); + +/** + * All RSS + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_RSS_ALL instead. + */ +define('SIMPLEPIE_TYPE_RSS_ALL', NamespacedSimplePie::TYPE_RSS_ALL); + +/** + * Atom 0.3 + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_ATOM_03 instead. + */ +define('SIMPLEPIE_TYPE_ATOM_03', NamespacedSimplePie::TYPE_ATOM_03); + +/** + * Atom 1.0 + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_ATOM_10 instead. + */ +define('SIMPLEPIE_TYPE_ATOM_10', NamespacedSimplePie::TYPE_ATOM_10); + +/** + * All Atom + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_ATOM_ALL instead. + */ +define('SIMPLEPIE_TYPE_ATOM_ALL', NamespacedSimplePie::TYPE_ATOM_ALL); + +/** + * All feed types + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::TYPE_ALL instead. + */ +define('SIMPLEPIE_TYPE_ALL', NamespacedSimplePie::TYPE_ALL); + +/** + * No construct + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::CONSTRUCT_NONE instead. + */ +define('SIMPLEPIE_CONSTRUCT_NONE', NamespacedSimplePie::CONSTRUCT_NONE); + +/** + * Text construct + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::CONSTRUCT_TEXT instead. + */ +define('SIMPLEPIE_CONSTRUCT_TEXT', NamespacedSimplePie::CONSTRUCT_TEXT); + +/** + * HTML construct + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::CONSTRUCT_HTML instead. + */ +define('SIMPLEPIE_CONSTRUCT_HTML', NamespacedSimplePie::CONSTRUCT_HTML); + +/** + * XHTML construct + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::CONSTRUCT_XHTML instead. + */ +define('SIMPLEPIE_CONSTRUCT_XHTML', NamespacedSimplePie::CONSTRUCT_XHTML); + +/** + * base64-encoded construct + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::CONSTRUCT_BASE64 instead. + */ +define('SIMPLEPIE_CONSTRUCT_BASE64', NamespacedSimplePie::CONSTRUCT_BASE64); + +/** + * IRI construct + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::CONSTRUCT_IRI instead. + */ +define('SIMPLEPIE_CONSTRUCT_IRI', NamespacedSimplePie::CONSTRUCT_IRI); + +/** + * A construct that might be HTML + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML instead. + */ +define('SIMPLEPIE_CONSTRUCT_MAYBE_HTML', NamespacedSimplePie::CONSTRUCT_MAYBE_HTML); + +/** + * All constructs + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::CONSTRUCT_ALL instead. + */ +define('SIMPLEPIE_CONSTRUCT_ALL', NamespacedSimplePie::CONSTRUCT_ALL); + +/** + * Don't change case + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::SAME_CASE instead. + */ +define('SIMPLEPIE_SAME_CASE', NamespacedSimplePie::SAME_CASE); + +/** + * Change to lowercase + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::LOWERCASE instead. + */ +define('SIMPLEPIE_LOWERCASE', NamespacedSimplePie::LOWERCASE); + +/** + * Change to uppercase + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::UPPERCASE instead. + */ +define('SIMPLEPIE_UPPERCASE', NamespacedSimplePie::UPPERCASE); + +/** + * PCRE for HTML attributes + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::PCRE_HTML_ATTRIBUTE instead. + */ +define('SIMPLEPIE_PCRE_HTML_ATTRIBUTE', NamespacedSimplePie::PCRE_HTML_ATTRIBUTE); + +/** + * PCRE for XML attributes + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::PCRE_XML_ATTRIBUTE instead. + */ +define('SIMPLEPIE_PCRE_XML_ATTRIBUTE', NamespacedSimplePie::PCRE_XML_ATTRIBUTE); + +/** + * XML Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_XML instead. + */ +define('SIMPLEPIE_NAMESPACE_XML', NamespacedSimplePie::NAMESPACE_XML); + +/** + * Atom 1.0 Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_ATOM_10 instead. + */ +define('SIMPLEPIE_NAMESPACE_ATOM_10', NamespacedSimplePie::NAMESPACE_ATOM_10); + +/** + * Atom 0.3 Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_ATOM_03 instead. + */ +define('SIMPLEPIE_NAMESPACE_ATOM_03', NamespacedSimplePie::NAMESPACE_ATOM_03); + +/** + * RDF Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_RDF instead. + */ +define('SIMPLEPIE_NAMESPACE_RDF', NamespacedSimplePie::NAMESPACE_RDF); + +/** + * RSS 0.90 Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_RSS_090 instead. + */ +define('SIMPLEPIE_NAMESPACE_RSS_090', NamespacedSimplePie::NAMESPACE_RSS_090); + +/** + * RSS 1.0 Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_RSS_10 instead. + */ +define('SIMPLEPIE_NAMESPACE_RSS_10', NamespacedSimplePie::NAMESPACE_RSS_10); + +/** + * RSS 1.0 Content Module Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_RSS_10_MODULES_CONTENT instead. + */ +define('SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT', NamespacedSimplePie::NAMESPACE_RSS_10_MODULES_CONTENT); + +/** + * RSS 2.0 Namespace + * (Stupid, I know, but I'm certain it will confuse people less with support.) + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_RSS_20 instead. + */ +define('SIMPLEPIE_NAMESPACE_RSS_20', NamespacedSimplePie::NAMESPACE_RSS_20); + +/** + * DC 1.0 Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_DC_10 instead. + */ +define('SIMPLEPIE_NAMESPACE_DC_10', NamespacedSimplePie::NAMESPACE_DC_10); + +/** + * DC 1.1 Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_DC_11 instead. + */ +define('SIMPLEPIE_NAMESPACE_DC_11', NamespacedSimplePie::NAMESPACE_DC_11); + +/** + * W3C Basic Geo (WGS84 lat/long) Vocabulary Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO instead. + */ +define('SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO', NamespacedSimplePie::NAMESPACE_W3C_BASIC_GEO); + +/** + * GeoRSS Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_GEORSS instead. + */ +define('SIMPLEPIE_NAMESPACE_GEORSS', NamespacedSimplePie::NAMESPACE_GEORSS); + +/** + * Media RSS Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_MEDIARSS instead. + */ +define('SIMPLEPIE_NAMESPACE_MEDIARSS', NamespacedSimplePie::NAMESPACE_MEDIARSS); + +/** + * Wrong Media RSS Namespace. Caused by a long-standing typo in the spec. + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG instead. + */ +define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG', NamespacedSimplePie::NAMESPACE_MEDIARSS_WRONG); + +/** + * Wrong Media RSS Namespace #2. New namespace introduced in Media RSS 1.5. + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG2 instead. + */ +define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG2', NamespacedSimplePie::NAMESPACE_MEDIARSS_WRONG2); + +/** + * Wrong Media RSS Namespace #3. A possible typo of the Media RSS 1.5 namespace. + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG3 instead. + */ +define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG3', NamespacedSimplePie::NAMESPACE_MEDIARSS_WRONG3); + +/** + * Wrong Media RSS Namespace #4. New spec location after the RSS Advisory Board takes it over, but not a valid namespace. + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG4 instead. + */ +define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG4', NamespacedSimplePie::NAMESPACE_MEDIARSS_WRONG4); + +/** + * Wrong Media RSS Namespace #5. A possible typo of the RSS Advisory Board URL. + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG5 instead. + */ +define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG5', NamespacedSimplePie::NAMESPACE_MEDIARSS_WRONG5); + +/** + * iTunes RSS Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_ITUNES instead. + */ +define('SIMPLEPIE_NAMESPACE_ITUNES', NamespacedSimplePie::NAMESPACE_ITUNES); + +/** + * XHTML Namespace + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::NAMESPACE_XHTML instead. + */ +define('SIMPLEPIE_NAMESPACE_XHTML', NamespacedSimplePie::NAMESPACE_XHTML); + +/** + * IANA Link Relations Registry + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY instead. + */ +define('SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY', NamespacedSimplePie::IANA_LINK_RELATIONS_REGISTRY); + +/** + * No file source + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::FILE_SOURCE_NONE instead. + */ +define('SIMPLEPIE_FILE_SOURCE_NONE', NamespacedSimplePie::FILE_SOURCE_NONE); + +/** + * Remote file source + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::FILE_SOURCE_REMOTE instead. + */ +define('SIMPLEPIE_FILE_SOURCE_REMOTE', NamespacedSimplePie::FILE_SOURCE_REMOTE); + +/** + * Local file source + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::FILE_SOURCE_LOCAL instead. + */ +define('SIMPLEPIE_FILE_SOURCE_LOCAL', NamespacedSimplePie::FILE_SOURCE_LOCAL); + +/** + * fsockopen() file source + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::FILE_SOURCE_FSOCKOPEN instead. + */ +define('SIMPLEPIE_FILE_SOURCE_FSOCKOPEN', NamespacedSimplePie::FILE_SOURCE_FSOCKOPEN); + +/** + * cURL file source + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::FILE_SOURCE_CURL instead. + */ +define('SIMPLEPIE_FILE_SOURCE_CURL', NamespacedSimplePie::FILE_SOURCE_CURL); + +/** + * file_get_contents() file source + * @deprecated since SimplePie 1.7.0, use \SimplePie\SimplePie::FILE_SOURCE_FILE_GET_CONTENTS instead. + */ +define('SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS', NamespacedSimplePie::FILE_SOURCE_FILE_GET_CONTENTS); + +} + diff --git a/scripts/getfeeds_http.php b/scripts/getfeeds_http.php new file mode 100644 index 0000000..eaa03eb --- /dev/null +++ b/scripts/getfeeds_http.php @@ -0,0 +1,167 @@ +<?php +/* + Build rss feed pages + creates the necessary index.phps using + /feeds/feedname/feed and /feeds/feedname/title + feed contains the feed url, title is whatever title you edit + requires simplepie https://www.simplepie.org/ +*/ + +require_once('/srv/http/wittamore.fr/scripts/SimplePie.compiled.php'); + +$root = "/srv/http/wittamore.fr/feeds"; +$cache = "/srv/http/wittamore.fr/.cache"; +$fs = "feed"; +$ts = "title"; +$dir = glob($root . '/*' , GLOB_ONLYDIR); + +$dt = new DateTime("now", new DateTimeZone('Europe/Paris')); +$curdir = str_replace($_SERVER["DOCUMENT_ROOT"],"",__DIR__); + +// build header +$header = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n"; +$header .= "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" >\n"; +$header .= "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" >\n"; +$header .= "<meta name=\"description\" content=\"my rss feeds\" >\n"; +$header .= "<link href=\"/style.css\" rel=\"stylesheet\" type=\"text/css\" >\n"; +$header .= "<link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" >\n"; +$header .= "<title>My feeds</title>\n</head>\n<body>\n<nav>\n"; +$header .= "<header>\n<h1>Bloggings</h1>\n<nav>\n"; +$header .= "<p>\n<a + href=\"/index.php\">Blog</a> | <a + href=\"/static/index.php\">Static</a> | <a + href=\"/search.php\">Search</a> | <a + href=\"https://git.wittamore.fr\" target=\"_blank\" title=\"Git\">Git</a> | <a + href=\"https://social.wittamore.fr/philip\" target=\"_blank\" title=\"Fediverse\">Fedi</a> | <b + >Feeds</b> | <a + href=\"/rss.xml\">RSS</a>\n"; +$header .= "</p>\n</nav>\n<div class=\"banner\">\n<img src=\"/images/me.jpg\" alt=\"logo\">\n"; +$header .= "<p>Comments: <a href=\"https://social.wittamore.fr/philip\">@philip</a><br>"; +$header .= "Contact: <a href=\"https://i.delta.chat/#ED59E209CCA23D16F40EB7F4AC0F8F0CFE934057&v=3&i=6G6xPiCMaT--yhMyn4r_1q-G&s=GB9pE6SoqzUpITR3XffnKQ1J&a=scu9zmbso%40nine.testrun.org&n=Philip+Wittamore\">Deltachat</a>\n</p>\n</div>\n</header>"; + +// start main index +$headmi = "<h2>RSS FEEDS</h2>"; +$headmi .= "<h4>last update: ".$dt->format('Y-m-d H:i:s')."</h4>\n"; +$headmi .= "<table><thead><tr><th>Type</th><th>Feed</th></tr></thead><tbody>\n"; +file_put_contents("$root/index.php", $header.$headmi); + +// iterate through feed directories +foreach ($dir as $subdir) { + // get feed settings from directory + if (!file_exists("$subdir/$fs")) continue; + if (!file_exists("$subdir/$ts")) continue; + + $feeditemtitle = fgets(fopen("$subdir/$ts", 'r')); + $feeditemtitle = str_replace("\n", '', $feeditemtitle); + + $feedurl = fgets(fopen("$subdir/$fs", 'r')); + $feedurl = str_replace("\n", '', $feedurl); + + // new instance of simplepie + $feed = new SimplePie(); + + // simplepie can't recuperate a gopher url + // so we use curl in this case + $gopher = 0; + $rssstring = ""; + if (str_starts_with($feedurl, "gopher")) { + exec("curl $feedurl", $rssdata, $retval); + if (!empty($rssdata)) { + $rssstring=implode($rssdata); + $feed->set_raw_data($rssstring); + $gopher=1; + }else{ + $itemlist .= "<p>Error $retval retrieving $feedurl</p>\n"; + } + }else{ + $feed->set_feed_url($feedurl); + } + + // initialise simplepie + $feed->set_cache_location($cache); + $feed->enable_order_by_date(true); + $feed->handle_content_type(); + $feed->init(); + + $feedtitle = $feed->get_title(); + + // extract each feed item + $x=0; + $youtube=false; + $itemlist=""; + foreach ($feed->get_items(0, 30) as $item) { + + $itemlink = $item->get_permalink(); + $itemtitle = $item->get_title(); + $itemdate = $item->get_date("Y-m-d"); + + if (empty($itemtitle)) $itemtitle="No item title found"; + + // build item link + if ($gopher==1) { + $host = parse_url($itemlink, PHP_URL_HOST); + $port = parse_url($itemlink, PHP_URL_PORT); + if ((int)$port < 70) $port="70"; + $path = parse_url($itemlink, PHP_URL_PATH); + if (pathinfo($path, PATHINFO_EXTENSION)) { + $itemlist .= "<tr><td>".$itemdate."</td><td><a href=\"gopher://" . $host . ":" .$port ."/0".substr($path,2) . + "\">" .$itemtitle."</a></td></tr>\n"; + }else{ + $itemlist .= "<tr><td>".$itemdate."</td><td><a href=\"gopher://" . $host . ":" .$port ."/1".substr($path,2) . + "\">" .$itemtitle."</a></td></tr>\n"; + } + }else{ + // convert rss youtube url to youtu.be url + if (str_contains($itemlink,"youtu")) { + $youtube=true; + $itemlink = str_replace("www.youtube.com/watch?v=", "youtu.be/", $itemlink); + } + $itemlist .= "<tr><td>".$itemdate."</td><td><a href=\"".$itemlink."\">".$itemtitle."</a></td></tr>\n"; + } + $x++; + } + + // build list of feed items + if ($x==0) { + // gopher error tag + if ( str_starts_with($rssstring,"3")) { + $itemlist .= "<tr><td></td>Error retrieving gopher feed</td></tr>\n"; + }else{ + $itemlist .= $rssstring; + } + } + + // build rss_feeds/feed/index.php + $index = $header; + $index .= "<h2>".$feedtitle."</h2>"; + $index .= "<h4><a href=\"".$feedurl."\">feed link</a></h4>\n"; + $index .= "<table><thead><tr><th>Date </th><th>Article</th></tr></thead><tbody>\n"; + $index .= $itemlist; + $index .= "</tbody></table>\n<p> </p>\n</main>\n</body>\n</html>"; + file_put_contents("$subdir/index.php", $index); + + // build rss_feeds/index.php + $feedline = explode(" - ", $feeditemtitle); + $feedname = "<tr><td>".$feedline[0]."</td><td><a href=\"".basename($subdir)."\">".$feedline[1]."</a></td></tr>\n"; + file_put_contents("$root/index.php", $feedname, FILE_APPEND); + + // reset + unset($rssstring); + unset($rssdata); + unset($sfeed); + unset($itemlist); +} + +// finish rss_feeds/index.php +file_put_contents("$root/index.php", "</tbody>\n</table>\n<p> </p>\n</main>\n</body>\n</html>", FILE_APPEND); + +?> + + + + + + + + + diff --git a/search.php b/search.php new file mode 100755 index 0000000..095d8c1 --- /dev/null +++ b/search.php @@ -0,0 +1,104 @@ +<?php +$string = ""; +if (isset($_POST["s"])) { + $string = htmlspecialchars($_POST["s"]); +} +?> +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>Bloggings</title> +</head> +<body> + <header> + <h1>Bloggings</h1> + <nav> + <p><a href="index.php">Blog</a> | <a + href="/static/index.php">Static</a> | <b>Search</b> | <a + href="https://git.wittamore.fr" target="_blank" title="Git">Git</a> | <a + href="https://social.wittamore.fr/philip" target="_blank" title="Fediverse">Fedi</a> | <a + href="/feeds/index.php" target="_blank" title="Feeds">Feeds</a> | <a + href="rss.xml">RSS</a></p> + </nav> + <div class="banner"> + <img src="/images/me.jpg" alt="logo"> + <p>Comments: <a href="https://social.wittamore.fr/philip">@philip</a><br> + Contact: <a href="/images/deltachatqr.jpg">Deltachat</a> + </p> + </div> + </header> + <h2>Search this blog</h2> + <form action="search.php" method="post"> + <input name="s" type="text" autofocus> + <input type="submit"> + </form> + <!-- list start --> + +<?php if (!empty($string)) { + $string = strtolower($string); + echo "<h2>Results</h2>"; + echo 'Search terms: <b>"' . $string . '"</b>'; + echo "<h3>Static Pages</h3>"; + $dir = new RecursiveDirectoryIterator("./static"); + echo "<ul>"; + $i = 0; + foreach (new RecursiveIteratorIterator($dir) as $filename => $file) { + if ($file != "." && $file != "..") { + if (!is_dir($file)) { + $content = strtolower(file_get_contents($file->getPathname())); + if (strpos($content, $string) !== false) { + $i++; + preg_match("/<h2>(.*?)<\/h2>/s", $content, $titre); + $title = "?"; + if (isset($titre[1])) { + $title = ucfirst($titre[1]); + } + echo '<li><a href="' . $file . '">' . $title . "</a></li>"; + } + } + } + } + if ($i == 0) { + echo "<i>No match</i>"; + } + echo "</ul>"; + echo "<h3>Articles</h3>"; + $dir = new RecursiveDirectoryIterator("./articles"); + echo "<ul>"; + $i = 0; + foreach (new RecursiveIteratorIterator($dir) as $filename => $file) { + if ($file != "." && $file != "..") { + if (!is_dir($file)) { + $content = strtolower(file_get_contents($file->getPathname())); + if (strpos($content, $string) !== false) { + $i++; + preg_match("/<h2>(.*?)<\/h2>/s", $content, $titre); + $title = "?"; + if (isset($titre[1])) { + $title = ucfirst($titre[1]); + } + echo '<li><a href="' . $file . '">' . $title . "</a></li>"; + } + } + } + } + if ($i == 0) { + echo "<i>No match</i>"; + } + echo "</ul>"; +} ?> + + <!-- list end --> + <p> </p> + <hr /> + <p>§</p> + </body> +</html> + + + + diff --git a/sitemap.xml b/sitemap.xml new file mode 100755 index 0000000..3159862 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,5299 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- generator="Milkys Sitemap Generator, https://github.com/mcmilk/sitemap-generator" --> +<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> +<url> + <loc>https://wittamore.fr/favicon-32x32.png</loc> + <lastmod>2026-06-04</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/translate.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/mutt-trim.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/display_filter.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/account_philip.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/poems.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/venelles.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/dwm-config.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/update-fingerprint</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/key.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/print_unicode.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/neomuttrc.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/get-mailboxes.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/txt/mailcap.txt</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/le-mene.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/woodworking/oland-tools.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/woodworking/Scie-à-ruban-fait-maison.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/woodworking/tour-à-bois.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/Mention-Legal.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/index.php</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/Neomutt.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/about.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/140-html-color-names.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/woodworking.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/summer-of-75.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/static/Interesting-links.html</loc> + <lastmod>2026-06-21</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/de/smolnet.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/de/index.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/en/smolnet.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/en/index.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/lynx_1.png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/gopher_elpher.png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/gopher_little_gopher_client_mac.png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/gopherus.png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/chawan_2.png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/gopher.png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/chawan_1.png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/gopher_gophercle_android.jpg</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/gopher-dark.png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/gopher_IGICv1.12..png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/images/lagrange.png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/fr/smolnet.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/fr/index.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/index.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/gopherfr-translations.tar.gz</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/gdpr.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/it/smolnet.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/it/index.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/es/smolnet.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/gopher/es/index.html</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/index.php</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.tmp/tmp.txt</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.tmp/tmp.html</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Taking-the-leap-to-Artix.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Daft-poems.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Hard-disk-fail.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Dmenu-updates.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/DWM-slstatus.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Blog-scripts.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Replace-sudo-with-doas-on-Artix.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Try-different-versions-of-Linux-online.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/The-Mote-in-God's-eye.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Screen-scaling-with-xrandr.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Window-manager-thoughts.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Oddly-useful-Linux-tools.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/AC7260-wifi-card-on-Thinkpad-X220.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Cpu-scaling-governors-on-Thinkpax-X220.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/A-fledgling-bash-function-library.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/dwmblocks-with-emoji-icons.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/VPS-management.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Terminal-Trove.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Debian-12-to-13-upgrade.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/My-gopher-site.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Betterfox.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Carbide-tools-for-wood-turning.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Enable-Arch-Extra-repository-in-Artix.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Simplex-a-decentralised-private-messenger.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Ladybird-web-browser.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/What-to-do-with-an-unused-Kindle.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/New-builds-of-dwm-and-st.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Thinkvantage-button-in-DWM.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/The-last-thing-the-EU-needs-....html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Linux-power-consumption-control.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Amazon-playing-silly-buggers.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Koreader-for-e-ink-devices.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Closing-laptop-lid-without-suspend.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Power-notifications.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/DWM-circulate-through-tags.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Thoughts-on-suckless.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Fixing-my-dad's-Stanley-8-CFW-lock-plane.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Annoying-touchpad.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Workshop-Air-Cleaner.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Git-repository.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Chaotic-Aur.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Greycoders-list-of-Text-only-minimalist-web-sites.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/TLP-recalibration-error-on-Thinkpad-X220.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Neomutt-as-default-in-XFCE4.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Wifi-region.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Browser-choices.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Html-center-element.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Les-Oiseaux-des-Jardins.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Toggle-screen-resolution-in-DWM.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Detrumpez-vous.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Bash-prompt-without-scripts.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/DNS4U-European-DNS-servers-go-live.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Install-the-Chawan-text-browser-on-Artix.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Dwmblocks.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Starship-prompt.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Gopher-it.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Mailcow-Docker-too-many-requests.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Updated-cgit-server.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Thinkpad-X220-Fan-control.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Thinkpad-T430-screen-upgrade.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Bringing-back-the-fun.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Rsync-backup-to-Synology-Diskstation.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Traditional-screenshot-of-my-X220.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Touchpad-deactivation.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Reducing-web-font-size.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Archwiki-on-my-laptop.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Xlibre-on-my-X220.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Mailcow-with-Nginx-reverse-proxy.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Thoughts-on-zsh.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Linux-System-Performance-Tuning.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Flowers-for-the-bees.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Back-to-the-future.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Upload-modified-blog-files-with-scp.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/dwm-windowmanager-2.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/France-rejects-backdoors.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Jazz-radio-recording-from-1976.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Slstatus-date-with-alternative-locale.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Slim-fork-login-manager.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Headless-Arch-Install.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/DWM-Dunst-and-Thinkpad-audio-keys.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Stick-it-to-the-man.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/dwm-windowmanager-1.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Naked-DWM.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/The-universe-is-spinning.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Ghostty.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Lynx,-Gopher,-and-video-files.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Shellcheck-your-bash-scripts.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Microphone-distortion-on-Thinkpad-X220.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Powerline-Bash.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Newsboat-and-gopher-RSS-feeds.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Why-does-everything-feel-so-boring-now.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/RSS-feeds-that-don't-suck-on-terminals.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Menu-editor-for-XFCE4.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Eza.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/X220-internal-Bluetooth-controller.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Pipewire.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Using-Goaccess-to-view-web-stats.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Mailcow-with-Nginx-reverse-proxy-(updated).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Anacron-on-Artix.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2025/Modified-dwm-bar-battery-script.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2022/Scie-à-ruban-fait-maison-(12).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2022/Scie-à-ruban-fait-maison-(9).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2022/Scie-à-ruban-fait-maison-(11).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2022/Scie-à-ruban-fait-maison-(8).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2022/Scie-à-ruban-fait-maison-(10).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2022/mailcow.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2022/Scie-à-ruban-fait-maison-(7).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/This-blog-wasn't-so-bad-after-all.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Updated-poem.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Filtering-the-web.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/My-own-private-search-engine.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Ladybird-turns-into-a-squashed-bug.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/De-Gaulle---Two-films.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/From-minimalist-to-smolweb.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/upgrade-to-dwm-6.8.html</loc> + <lastmod>2026-06-30</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Funeral-blues.html</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/awking-around.html</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Installation-of-Snac2-on-Artix-Linux.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/More-on-Dillo.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Hebdogiciel.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Updated-blogthis-script.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/smolweb-validation-in-Dillo.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Oops!-I-did-it-again.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/displaying-MATE-on-a-1366x768-screen.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Ghostbsd-installation.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Web-sites-that-work-well-in-Dillo.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Living-on-Snac's-bleeding-edge.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/semware-tse-editor.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2026/Freedos-thoughts.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2024/Another-Oland-tool.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2024/support-percé-pour-bougie-chauffe-plat.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2024/A-new-lathe.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/test.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/new-television.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/connerie-des-dates-limites.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Marginalia-search-engine.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/ms-eu-fine.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/password-store-to-bitwarden.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/t430-cpu-upgrade.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/fixing-25-second-firefox-delay.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/noyb-files-gdpr-comp.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/newsboat-strip-junk-from-web-pages.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/saint-catherine.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Need-a-long-walk.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/minimalist-blog.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/prettier-when-it-works.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/micro-text-editor.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/periodic-fstrim-on-ssd-drives.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Death-of-the-web.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/kind-of-blue.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Little-Doggies.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/images-in-xterm-emulators.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/drm-et-firefox.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Firefox-Multi-Account-Containers.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/searxng-on-my-laptop.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/mer-des-faluns.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/ascii-art.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Chimera's-Star.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/password-store-remote.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Bitwarden-RBW-blues.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/linux-paradise.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/debian-or-arch.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/searxng-a-private-search-engine.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/system-collapse.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/In-2024,-please-switch-to-Firefox.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/map-of-langourla.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/windowmaker-in-1998.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/obscurantism.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/thinkpad-t430.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Gemini---The-protocol-not-the-AI.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/1vyrain.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/updating-my-thinkpad-t430-wifi-card.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/nuclear-sabotage.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/pacman-yay-notes.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/protecting-your-computer-with-a-hosts-file.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/evil-empire.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/t430-cpu-upgrade.html~</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Forget-VTech,-get-an-old-laptop-instead.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/windowmaker-0.96.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/hardening-firefox.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/jersey-before.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/quad-9.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Web-1.5.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/Web-site-stats-better-without-Google.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2023/radio-jazz-soul.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Project-Hail-Mary.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Un-espion-dans-ma-poche.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Le-Jardin-de-Tonton-Loïc.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Jitsi-vidéoconference.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Expériment.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Invidious---une-interface-alternative-pour-Youtube.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Scie-à-ruban-fait-maison-(2).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Boxe-pour-chevaux-jouets.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Scie-à-ruban-fait-maison-(5).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Bol-en-chêne-de-28cm.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Bibliothèque-Nationale-de-France.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Thinkpad-X220.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Coupure-microphone-X220.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Wiby.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/TLS-handshake-failure.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Arch-Linux.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Nginx-permission-denied.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Alacritty.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Intel-Thermald.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Photo-de-mon-atelier.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/web-bloat.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Mini-chariot-avec-butée-d’arrêt.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Certbot-delete.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Thinkpad-X200.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Zellij-Terminal-Workspace.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Scie-à-ruban-fait-maison-(1).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Scie-à-ruban-fait-maison-(4).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Planche-à-découper-en-bois-de-bout.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Fabriquez-votre-propre-ordinateur-portable.htm</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/ZRAM.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Odysee.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Update.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/wittr.in.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Scie-à-ruban-fait-maison-(3).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Nouvel-établi-pour-l'atelier.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Mise-à-jour-sur-la-régulation-du-processeur.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Télécharger-musique-depuis-Youtube.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Sous-plat-avec-incrustation-epoxy-Milliput.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/erreurs-spamass-milter.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Grande-vase-en-chêne.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Étau-de-recup.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Mbsync-push.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Une-balancoire-pour-Barbie.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Lnav---maîtriser-vos-journaux.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Science-et-Vie.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Ce-que-l'Europe-à-fait-pour-moi.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/spamass-milter-error.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/i3-Blocks.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Fugitive-Telemetry.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Scie-à-ruban-fait-maison-(6).html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Nerdfonts-in-i3.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Arch-LTS-kernel.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Pyradio.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/GeoIP2-et-Goaccess.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/La-grande-illusion.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/articles/2021/Deux-maisons-de-poupées.html</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/search.php</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/README.md</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/logo.png</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/files/bulletins/bulletin_062015_web.pdf</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/files/bulletins/bulletin_122014_WEB.pdf</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/files/bulletins/bulletin_032015_WEB.pdf</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/files/bulletins/bulletin_052014_006_WEB.pdf</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/files/bulletins/bulletin_092014_01_WEB.pdf</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/files/langourla.pdf</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/sandbox/webproxy.php</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/description</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/push-to-checkout.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/pre-applypatch.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/pre-push.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/pre-receive.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/update.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/commit-msg.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/pre-rebase.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/applypatch-msg.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/post-update.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/prepare-commit-msg.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/pre-merge-commit.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/sendemail-validate.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/pre-commit.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/hooks/fsmonitor-watchman.sample</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/HEAD</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/index</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f9/983e3ff58a2dc21e875884952b01eff2f64b70</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f9/675574d64e6ae32098dc4e72b3bbd5d521f05e</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5d/fd323f605e3a11669283381d85dba3588d4bc0</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5d/653d48bd7b15b5f5358487621634c6a9222966</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5d/d5b0ae33d80935ded0c8b3c28c07b14a03596f</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/86/2574e3290bc16f6e51723b657e283c39ee359d</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/86/7c01b878035cf3b4262eadf59922f7874f49e0</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/86/b4f6c634c870bef36c635a26a3ff61db404f14</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/86/17440c07a8d935ba432f8400ef3c57aed49279</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4e/ba85d8f1013558f629deac086468e9af4e8980</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4e/5f9e6fb4f9f2d4fb7e64a8cbf34d6b1622386c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4e/9cd710bbad8f9b96a840a68fb131b9bd09fea6</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4e/bdc8a17e09b7cf6453e1707fdc8495ddcf2def</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9e/42c673e0fa8fe277ddceb3ff34e4e943d9c782</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9e/04620494d5422eb04ce4636fc662d957f6eafa</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9e/56430d76f2cac1740515a4b5047530eaee0d77</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2c/f0b0cac192c8d11beae676de85a9cc62898379</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2c/ab0f867405b2aff5366e92e381b6c511028bb6</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0b/84d2e6c45c69f0bda02642814893f4cef8f80d</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/3f/52033d8cee47deb876917878c8e36065390fd0</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/3f/ddef5256a3e46154732d84f476ffcf5c05e314</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/3f/cad170d9cbdaf9dc9590acd914c43988637a3b</loc> + <lastmod>2026-06-21</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/be/392a3749ffb974af88cfc3773ede9484e9ebc8</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/be/5adf0f2148505b5d75059294b2aaef3b99a3c3</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/13/1109a9736a685ca2bf2fbf410d1352a0ce5829</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/13/edab4b26ff8480a75bd51aba755b326e951329</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/79/24596de5ec6729d1c1bc7ddeb523011e9c74f1</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/79/e760a8cc7d964fdef0edf54d68eb452d54e4c8</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2a/1957bfe5e3b564b153597acea78751c3866b0f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f8/5c1c7ca6935e85124a618dbc45eddf2fca68a3</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f8/4650022c5637a0a4350e558643829be56ed83a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f8/e2dc53a22e17ed2c9bc5f47bb18a3c29d16c91</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f8/c0906a30e3da9caf21a39f70b67df32202dae8</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/06/97a2a8b0371f7194289311d5c63c144f474274</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/06/3e2facb1605fb39ef876e30153d7398d4e9f98</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ba/2bf469b9172fe526c7a731093536681d40fe83</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ba/f14adcef2a37b5bdf244b5dc625da5251566c9</loc> + <lastmod>2026-06-30</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4f/ac5228fbd9731ad519d3a8fae88add6984f8d2</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4f/9a36ae460884f0eaccfa865fba5b7d9e22c05e</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4f/e056ca464a10e2f666d850088b16c8789c8c40</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4f/5a6c98a7f0e7d491b9399520ca3c6fe8db2389</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c9/a41c714c7cda7b5ed2b04853ec91a5c2cb2278</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6f/44a38345630ee3873e5134c4f843c49736c929</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6f/f4bd53fef84ba00945e993f54c0e6444c58d43</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6f/3dbc6163ac5d3ab9b10a63224eaaf20fb74a42</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6f/c9fa98c3249b6c0dc57dc141e276aae8c9eabd</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e5/52cfaca8a32043be375c5600b5aaebb1969580</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/23/3528dc4ae216aa2f448159d31198dba893dda5</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/de/ae85e9ed5e09326b79e1e8e3198d2a78429ecf</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/de/b2a82a82aafdbd2edcfd99b2b0051cb88efdc2</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/de/4328277eeb253293546b0d57474d11960972ee</loc> + <lastmod>2026-06-30</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/de/31e0ef426d217811ec252df28a9e5ef516168e</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/de/f468f3507ef4be19031586a7fda8d76e1afd07</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/de/8bf3cdd7a5ffb7ee333b5b674ebd853c011f65</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/de/76d9ac869dbba17c558e18e538c6635ff9b7f5</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/de/dd238f097c358db58e44555c2e8ea658bc4807</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/27/e5a6367505901a24786cd8c79ed69ba7475b10</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/27/600e31730cd2b4666e0d39aced5a46f35495a9</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cf/6c42cfa39c4f28019e0706844f2dce3fd5bab6</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cf/3314e4f82bde37ab86a6fb571549b4fe6d580b</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cf/9985cd9a914ef7f0090b944ac1590bafda9db7</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cf/55cc910290b0840b97a32c4695a8f556b580ec</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cf/aaa9a4f9822aa5e6cc9ff293e3b60f9efb2f9f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/48/ad53323acb53dbd888b9337b4a9398ca51b31c</loc> + <lastmod>2026-06-30</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/48/367c600b24b114fa333c611fa791e4b59a5083</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0a/d535ddd6ec67a83ac05ca47ccb0615dccac063</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0a/2dbe0ee5e39008b70b6e3b8226db21a623256e</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0a/94d162d85bbcc888c4fa0652595f99011576bb</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0a/697a5f33e8588c7b3eb53f8f44c74226e4f7a1</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/94/049ebd4d2918ea2fe7d29026d04d8ee0455b78</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/94/1fb3fa9b3bb59d63f41fd8ecaad760dbfb6e93</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/94/c308f2915fb55b680930e700dc33b334225300</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a2/ad67ea2197eb622cc6c40ab40f9151dd12b374</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6c/ce186dafbf1e11920eb32c1e68088ff226b744</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6c/a648e41818506514f37e7a0519743474b4d475</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c8/0535fcfcd4841f2ca9776db6db52ba5d0d32ad</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c8/a2312d29d3c6c55fd2f138147cabf3750c8eea</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6a/3ac97cbbd04568d46a27c9c8d15dbafa9977ce</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6a/4a0362454c7dd6cb0f5cb81015c7015573406e</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/07/931b8615a56a629811c5dd51e2242eeb96dcf6</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/07/10e755af913936eacb4e59935ab4415668b3a2</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/07/561c6bddaaff6d1fa6e30b7d811d22604943bc</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/07/fd6401dbd2226bc93f2b08b5f37362fe322356</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/07/f5b9c7d36a3b74e21509c7857f86a0098d3081</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7d/38f741caa2b352e25668b61b8a1549ccca224e</loc> + <lastmod>2026-06-30</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7d/cd6a437acf03b6e73b683e8ef73d824c4dc8ac</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f6/86bc96e8042bb13d3bbf96dd98132d83a42206</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f6/9f10a5679656007f7da19a81ad28c954f20bae</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8f/2bedb3be266a20f33aded915ecf7793f38a09d</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8f/d66a0b059cb6fa7e5480fe544438ad255ebda2</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d8/cdfc07e729ad8d2ab0b5ee7a6e23e59bd27958</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d8/6634b4deba973643e3c8b8cb450875a33b33e8</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d8/8e91c2789fe1f1ca4ac5bb30acb07790ac836e</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d8/b483a9581a6e8fad67a8e47cad4cfbd3a1de7c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b3/e8dfaa6d5810befb0d904d0ab59d083c3f638d</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1c/2ff6db14217b70125afd8e73eec3382864619a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1c/db2e1add45a04fdadd0d867509050a89d70204</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1c/cf0648cf2db85dc8fca26547690d4b1a4f11e0</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1c/2d2d8bacf000a259e632be2cacc5d18e25aefe</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/18/e96676f4e8b3ec1948d1d0c3d6f3606747236d</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/18/21b3509188ac3430fae12b79a47025b0c9c51d</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/18/5768ad6c635d1b3cda2df9c2a21bf649bb4694</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/18/bdb95df30e7752f4204a7930aaeb9cb95397a9</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/95/bf4ec94b8f6fe270851e3eb9034d4c42cb9e3a</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6b/731834b3da65508e3877acc08ad082d2fb984a</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6b/82633a89614334e048d94c77f834229e5b2201</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6b/96c4fcbf7896d13a89d8311f41196925deac38</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6b/a27a4f35a0181c00e72ccb8f14ec5e88d36a97</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6b/746539f4ef5b2da65a4e5dc85baaadff74923a</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6b/b2ad74ed424497c11c8cfffb1fe96926981813</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/81/5cf994726c9bb40ac49ecf525c132c355da0e8</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/81/464a231c65d1d44ef71f5fb97f55464d22d045</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7a/2b4692fd587b26f40f2ebaa2ff5c26b753e885</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/65/dfc09944fd7a81ba52002b90d78342216e78cc</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/65/7b2147d5521b2533d8d0a9a3a59736030d8e13</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d5/9d1d038bed95ff3f29dbb81be5b1e6d6abb6af</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d5/2ee90b7d4f5750e7dffbad13dfacd237f643ce</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d5/13d6bf0af3171fd929ec3be048e1562696aaf5</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/64/2d2232cbbfd51889b368fe1a36fe0ddc0f2cbc</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/64/11b5c4ddf74044a342b2de469ec5884cfcfd7d</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/64/72a9fb1b4f7f58c9c4787d5dbe67611e25b270</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/62/f9c50e674e4e1290eacb5a9f9903c94a672df5</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/62/4a53a1ac1370e2edd244177271ea11f9451abb</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/62/884148ed7c74407e0d98813b77f6f06eacbce8</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0c/309f91f4bd11ae8a3f11f3cbd04486646719b2</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0c/5f0642a8b51e77dc9cb84a200e0653a8be5fbc</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0c/e8563fcaa96517e0ad7b52813d9ce79d35867b</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/3a/f117345317bbcb22468db1ddb5154cad64608a</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9c/8f55d32cd2e4c6d70509c8aada25a474e38f99</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9c/418a855dfc24d4b456e07d01c19fd0f857b58f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9c/67454185ff2c1e535766d14077cb4679d1dbe9</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9c/c6224194652f2d4bb2af0e94345348bdedad25</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9c/741a0bfc0d8ddc9569670f38b8dd9ae9a2cd26</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ae/dbed5d5514fc555ded66efd7eb9dce2f24d5a9</loc> + <lastmod>2026-06-21</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c6/cd8423182943d68a1799b4864d5496b6649336</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c6/42c6995164c759792959421d24c46e6d7492c7</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c6/039f778f76549230852c08664cd696aba96043</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c6/5b1b2885c2995b56e5e3c0a236ba5a568df90c</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5e/f5b9a7747bec23fab7f7125c9b1817d1891f43</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5e/8b638c362546af94e412a3bedafe5667487f10</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5e/2277218ff226ce20e36a8b01609af0a80cba05</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5e/4a7d0708a0afeacb042f7197506a9e0a1161e8</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/69/13c37cc1e267d7d2a910e1cf6b247c39d9974a</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/69/2841443d78e3d28704c8f8334ec6e173230f55</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/69/85bd66acaa98ad6e6dd53a86cee9a6379fa11e</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c0/97c7a1b5bd8bd6e12b55dee78e7262f97690d6</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/3e/c563a11b1505e00431ec5bb97468e0d926b47c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/87/83ecce84bf7838b13ec0598bb37ef3a6a64444</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/87/09fb3ff5ae5eaa7899f149246ffa8763c141ca</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/87/483579b1183a96b064be4a9d42494be1d67f9d</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b1/4a22dc9ca3860cb734eef7ca16961f69eb9f94</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b1/c0bcc745c11bb483fc354902db526b89939723</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b1/46fd8e2d17960ce0c861b9922a3701d4b1de28</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/50/cdf83ad7b885abf5061e3721e987af30b1c5cf</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/50/4c03ae1f16fa226ad19d01a7681cb92154347a</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/50/fd7cd1012bd9e33a7e9645ce7a2dccf7bc7e8b</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/70/a6aed8ea76b069324aabfb2bf9ea69560c4534</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/70/257915b05db517f7e31db14fd1133423b96b98</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/70/a68c71099bd18dd2c0a38933f76047eaa87ce3</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/72/55506c39051cffc91bae3ad551da14527d9733</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/72/ce0671ed8f8654da1f1b56341709dc83abe56b</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ca/4837908940235fe3b12e41f452dce2712157ed</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/09/5ecd9baae228765fce32eddebc9057442a2882</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/09/d09cf6cffde3bf2041552067c561449f1a88d1</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/09/e9cfa8d92f99028c383ac8b6f40a133b49d4fa</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/09/5d8c1f0a9b2507c2086af87560365d618da44e</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/09/ea597fa17cf6a4469319a54df0bae31220c375</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b8/37c732027a5977e553f7d4a11c1123e14731c0</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b8/e3c39c08eaee824e0f8131b5c1e8357650629d</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b8/6b01638976f2ddaf51000afea11714abf074a4</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/26/d066c62ad5c1d098c96ade780ce04af7d2cefa</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/26/977b67a916ed90c89722021a357df1918103f8</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/26/f842bd63bdbbf7a75081493b03c780639c727b</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/26/5c6edc12246c88b3356e5061cb6d5e0128771c</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/26/00f5e52a2ab65b666736842ad4ee1b3019352d</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/26/c811771410f7633f4c1c96e7c25a0d123859b1</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a7/05631eea4f14d1986248f86e845e7c232f25ca</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8e/871873b8908134c854e01d432c21bafb829bc4</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8e/c8edf0bba5eb1fa0a71503536f379272edc8ef</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8e/0c19663afc70e68b4addb2d8f7236992b70bcd</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8c/4040a68bd13181a7aabef159ee97ebae364f24</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5b/ca7217a8732e25dd808eef8613f85339e753da</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5b/19d0608e57c035cb3afb82283733d67a684a0a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5b/e3c14381ca553bad2493c7c18180db4333a2a5</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5b/f9069beaeafc990f40e4e790fd8997c4581f7f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5b/29b534e2723479cadae1f14e556fa1085d5a5c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/33/f9e66069d9f24903c44029218594a42beb3169</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/33/6e3ce4f22420b0eee6db3309c482ccbcc7b1ea</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fd/f4f5f5849b8db66c9eb1fddd8636b60c6b4ac8</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fd/c10e4a6988d65d456f8c2d0cafbf6f382ea9e3</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/05/cc804fa4b59e088054667010bc048843d423eb</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/05/fb9761cc6811ca5e90d71e7d2231ec72ef6b9c</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/05/dd1d62249db147ae785400c7e8a2535b536444</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/05/5b70c17495b5008418033951f27b1826bff308</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/05/8d9996b3148a4898d4f5c94392e8b40ba73765</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d3/757bf3c8e06095209cf850783c31e30318045d</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d3/ea26f02bbd1183bca812b5a949d175afa716c2</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d3/a57092c5741af41a036d88b844ac6c7bc78598</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f3/6fa76a14ea86fe3e1af47d02a6cd6f9a35de3a</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f3/d40674c611d201dedefe623afac9e5c4384745</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f3/116a6a268901dc442724261918deb2003a28af</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f3/670b482b8e7bd5be869c058b7d787234a18c9a</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f3/719a48cfd298389ad2d0f89dbc3da362446a59</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/14/4152935ede3fe6f4bc85ad0cc6a6827d571320</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/19/4913ce72fa9d632775f380c96f2788a1d94743</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/19/cb8afc238985944ac5220942ec0b384e319e54</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/19/b071100089a03028f085b244966f25af672c9b</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/19/87f9d6f2200a0b8683cf973051f891e9c93b4a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/19/b2e8ba213a99515223e2e04743a16a676aebc1</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/19/e37543925e5b665c464b1a93ed677838bd7652</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/82/01e7311cd9ea2bda62f6a468ef63631aa685c2</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/82/051cf5d52af65d04326d91ceb229627c3f8dbc</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ed/725dc29c101aae2858cb59b00936b25006913b</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/aa/4fcdbe36e600c4a4c69432e52a130bace94973</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/aa/106eac36aa7658e04fcc01c4bd689211d8c36c</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/66/958acf1e6d68d13fb09bcbd7d2361fe37f9b78</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fe/a0b5716958f01161382ea1ff1d655bb0e87fc5</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fe/5248a7f88362d84c5ed138b52be51c39ac6515</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/44/2985ef93d5a59e1c43dac0c421e8fa8eb6665c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/44/fc86eb1c64d882743b6dcbc094dad8d1e17307</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/44/e7ad1fa6fee0a220808640e8356a044872d1dc</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/44/0864d67a5598e78370790120b18071be743ada</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/44/1df1020f09a470382bc55660945cf650af77ad</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/44/6ab11ebf9221c9222b9a08671b1a8b35c81faf</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/45/5d8b81126cbe004b1f36e89010235404af40e5</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/45/bb9a6343c2f1bd23b7334bc80aec2b0ae89e57</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/45/cadd6706af38122896db139760ebcb5208cb71</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/45/6a49e2583a44e36320723ca3248c81ec179c7a</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5c/9d131deb9f511b203aabfe915047411d4e6a97</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5c/da9902f3725eea2d72fbc33976e21ea358652f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5c/e819707d03004e9e9dc97c7a4735ab73a36af0</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5c/662c291056b829fe4cd490255d1cc98fda1a86</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5c/ca9ff7fb6dd5caa55b493d4d68e9594c8aab55</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5c/d95deebb48d2d0e00347f9018a6688ea859355</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5c/0dcfc729b8a4dccecb27790102bdfa6660cd0f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/99/3c38dce8b81cb604b2024747212d241edc0927</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/99/2bad84d4f5fe98907f8570f3fa9ae957b35e8a</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/99/64e4005ce02aea1f03098b387c69f6345de9d7</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/99/3d3f70dcaf0455da33d3e05e287594a1de8bcb</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5f/0141b02a5f9a55a01ef8a3b5802e093e28e542</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5f/5e4f23db757090988e735c8fc0f8440f27d3fc</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5f/9877ca6f46029803df1c9d40800b5d673ae786</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5f/909c626ce3388fb80c953ccf5fc8620708ac50</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cd/a3230d4524d912730fb2aef1f6e05cab322520</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cd/9e9a76ff0522032ffec701a34c87c5f7558149</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cd/e22fd2462f715533fdd199db4a4e8c822fb98d</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a3/1d0395b026bb062edaf7c1b80397f9013cb5dc</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b0/d1af60e115b72a0785fb4ac8415236f5cb3b30</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d2/b30a1624a74cc06b75bbd776b2dd73126794f3</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d2/2a3f397ea036bab09841f0c9286cf62ebb0047</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d2/fa203495d89cc11cb3274c6eca68e96a28f9ca</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/bf/74ad0d3432246acb8ffd5cf79d6b76e66ae273</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ce/22529c37be8ae4e85f6bcc24d44219de5310ff</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ce/a64a97b8c8a85b8e017c31ea31007d71b88a9e</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0d/73ef84f91e8fe430c3d21cb6b5a462e204f716</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0d/6475aab428de7f0d2a0f9ba026a15b90a4e56c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0d/a9364c5ce4122e7d019c434950d40932dacff0</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/56/f85172fcaec95b1cc7519247474c6e72c2189a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/56/27b2b037183a597b19dad6cc2813d0cd1ac18f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/78/ee4676894145d2a9f26adf84fa1eac53f7b04a</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/78/2bbc8ca1acb44191ad0c16c982fa8b4a3ccd47</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9d/3ea44fba0fbeae4faaf7085285d715f5a6c5fe</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9d/09cf0fd9f50761db3ef79263927cbc144cae9c</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4d/67fe09a54ed25f598ee3a2521704b765423615</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/32/b9a6224ee889c1cc8ef708fdedcc17e84b4730</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/32/888f65ba5c3ae0e6685dd27cf28069f519b764</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/32/2b44290ca3abb41a51d408a4abdeca5e62d57f</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e6/2e6d88f4885bb2992409ea30b162a28c61155d</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/53/71de9158df11194093095a968ff1d23afe74a9</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/53/dad3151553674cd2bb2d177ad4cae71d544988</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/53/2b5cebc7dcd987a7260eb48af2ed9ed6b8dc07</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/53/ce0d7f213a86908264db3e3bfff8486051b6a1</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/53/b9b44c8c5eedbfb1a7c72efe59df394780b10b</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/53/45d76b3743116016e4aa61a0839d557ca35227</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ef/bb467db57f90378bf18e4788bdb3814c928cc7</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ef/bdc9928b47fe1c4cdb3e376074906f920b43b7</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ef/f80717b26544cef3951e081bebdb70000148aa</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ef/6153d7882052af62f6e02046e7cf42cbc8fa4f</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/pack/pack-855f5d6bfbba675baff000e12376878a204cf1ba.idx</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/pack/multi-pack-index</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/pack/pack-855f5d6bfbba675baff000e12376878a204cf1ba.pack</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/pack/pack-855f5d6bfbba675baff000e12376878a204cf1ba.rev</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/76/d9c6427426d172ecfe368f2f646c276b0276d4</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/89/45b9c50b6f695410134213cab117907ce0dfb2</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/eb/6e25de5c4e19766ec7472bed9ef30397f4e60e</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/eb/14b39bd6915a8be50f30257f61268027b4e3a1</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a0/a850f66e4671414dccd444524af9e8c44c7869</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a0/ae57aeb7fbac8a818d95fe4aa72bcf608657b0</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a0/f1c7fc2a63819230a103fc8d4a62dbf8d4e1f8</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e1/91fe235f1d80b8e29dd20f9f6d7a46b10299fa</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e8/dfed34f97820ea444b3931a39966b8dfedebef</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e8/6ed19f38ff2001bde5e69e7141c6b95344b9ce</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/02/b4167fde53f2920ff89cde7a257f03ba4f3b52</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/02/90d70d23af724c90bde9c3c1cc2b4ca6c32f49</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f5/f0d5a3591d1efc99dd5626fcff8dae59d0dad6</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f5/66d657befedf00c2b0a3177dec91e0957582de</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f5/bb900912cad5605e7579132b2b288319698dc6</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/28/9b3de579c3ed8cf4b90823ff6e8d97527c84d3</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/28/4e7bb5cb78457b0624b3ac9be5f1c5cddd3019</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/28/46439526f2cfd5babb81570a9aa2bcaaf247df</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e9/083db631f28fd5a966537fddbb5e5128914f89</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e9/1c9ba1df69a40fdfc031f24327ddf634cf1b0a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e9/75116588a0a2abc1d3165822003641b746e385</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e9/6afc314b0aab34fb25908415e942ef6c8bc8c2</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e9/2a3b55bee6018b377f676c1dc2fcb033f97931</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/63/34bb69feb740f049f42fe21066c23fe0eb4dc8</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/63/a8281f95700754fea74120d8eaacd8f5de9b6e</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/63/c147e808716cc2d798bf786262b26b57d5acf2</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1f/3642d6cf6dc5fac686ed336306c761435cda13</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1f/ff35e15df4a6846e16d7076cafac8cf64956a3</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d0/0e625500a5cd51b89cd23384ade32626dd5845</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d0/10d072615e8178da792f5e3ba111e0c7e3f306</loc> + <lastmod>2026-06-30</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d0/5bb5a565f810efb883c31540ce595925727681</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d0/2e94c5bdcb5f6238a3747e0383079e79257ff4</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/90/943dd4f7a06a34445d60ecb861e40f6d7b4b73</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f7/fbf4e244eb30ab7f491944b80d92659c886d6c</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f7/82ed0c675ee5ce03cc705d95ac83453ee065d3</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f7/e90a2761e791a0b5e38da14e6c734fc7b7cf10</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f7/d6fdcf88076aa489bba34f957061dfd1e0ce53</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/38/e543d8ed289c4fd29b5b9383548fa730ce1e20</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/91/408341a1963a3ed01124a896cd34a19ad689b3</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/91/f77512b6691982dc60f85b299386f5c5ceeedc</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/91/6016639aff0559fdec450b96a5549f6410e4f4</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/91/655d19c8a5e7a6e38ff7d6b2b23e820678108b</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1a/990e0a142b2ae56cf6e6b48ae835947c48d1e6</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1a/5d5dbbca754f0726fa444de6dc366e3c9c891c</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0f/6bbd043dea365daa5f1146f87f2499a6ef721e</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/0f/9f713fa0553f7dffba2eced6dd1a82d70a6b34</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ac/853ff65d8733809bb38d21238ee1bb01853cb5</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ac/1b05b182cdf8daa84ddf918735ecc614c23caa</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ac/c969c28c65e1c46c79c0933851d82778ef6953</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ac/889318b141f23eae871293031e776b804826af</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a1/1e91d0d9d9c86a2acb0de6ff9a3dc60b96c808</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a1/7ffc6ac3e157b39dea431baf5c64fd69c5053e</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/36/ff5c7a0ffc61a4f09ccf3e2b5cc969fbaac02a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/36/488fdc91dc87248f7c860115c1a47e8e10a0f6</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/36/d888d333b2bbcd630274a45bfd51be89cf011a</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/34/5ec869e6322addf43a11851da0fec61fbba00b</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d9/2efd5da8e6c4098a8cc111b5529036414fd5af</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9f/21320ca1e6024ed6e6e66aad2731036fb61c1e</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9f/465b96cda0f7da8231066ec71e0ed7cc5b0362</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9f/006a350eefbc0362283393995c77748450137f</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ea/39a29cce9d8c0a3b057e666b2398f2cc1b3e44</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ea/8ab0436c9cade0a7e6f53a0e2971655a55828a</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ea/a03eb9151356d0be549b121206b589b770cd65</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ea/e9c4d6fc98eba05ff8f07ff1355ac43ead8699</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c4/cc235125e7a2bff650d684f5a2f43b5b9d35db</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c4/e596008b3e687b788c628c92589618c2d30e51</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/bd/3bf4f2100537deea3abe24680d4f3aa03037f4</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/bd/6fbb760c2deeaa6d54c783c3524deb2659b8b1</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/bd/31c21703604d84a7a40430cc947f1f56c34b65</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/bd/19b12622792c8a5062f15d0f58e6691fffc38c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/30/eca336d792163391e001649b6ea114c2349782</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/30/7a2c5b887c64bbbf7af1a44db9b8ac1454b45a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d6/6017e35cd5d9678e005ab22b519c580e194994</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d6/ae2f0e2bdda1f98763c4024092e8411c267227</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d6/ce71e278b77243c2fd0cf9f091b4cbdfad13af</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d6/c779cfc7dc1f74599d4c08e296a0893da0b3b0</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d6/db01b7c1ddfb758385679371ca6e011e6d74c8</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/51/9d319901e1654ef099ed73a249de6062d79e28</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5a/fff0ddb67ab59c4cbb4ca97246fc9a9aa8c4bf</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5a/6ae7ce69987328287b7b982e99be9b8bf15ba0</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/5a/7ab1ddca94409d98e08edaf977c13a23de76e5</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d7/d553146b10f50099c4454ec0355deda865b94f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d7/58c9f40a8ed03c4a0dfd8080fa2f22a1928554</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d7/139a9cd54223fb4dcc6c0b8f5886498aa7cb9c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ff/0462ddece820dcbb501fe67851a66087e7ba20</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ff/f965269099a11741b46eefa91b0cb8dcb954cb</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ff/be0c452b87b78b79b99dd626a80aeb3dd94143</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/dd/2ff68c1ec6dd8adc55536379a26e21e2c0bee8</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/dd/564bdbe6a87780f878c47b087accde1d837c6e</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/dd/7588531a0401a72f64d6307a97273c437ffb60</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/dd/8f33777c4f9b4076bfcbfedadabc01bafcbeba</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/dd/7df4b07c1585c007aa2b56ea7968792d69794e</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c3/67bb3a19715d293ca99c6bede19edbe2fbff05</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c3/4e17ac641aea6e19bd8167c3fa1072935d69d2</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c3/72098b3fdee94ea0f43bced0540d75ae38f4e6</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c3/bc3d86b6fd5bb0b50cac510be9be17c9481281</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/31/748e0262a69205b8bf071f207c15b1897270ee</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/31/b2a0f2de61f0686588d57c447da67907697bf3</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/31/0fef3b2a6a56cca95bb500c50506c9f4975562</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/31/f4f9b4d991e797eecfaf06fed02ddd5d63f06f</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b7/ddc13e14e35eba7b4c8e5180e921c13a53e3fb</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b7/e579fdc6f6e06f62acf78f5051b2082252b595</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/80/a2205d5835583135f3eaf2cf411e39dae3fd12</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/80/caf58fdb10b72534bed89689eef49c7f9ff123</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/80/834fb63a4954d90c4a68a005c9c95813b0fcd3</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/da/f0327174c3634e0ce272dfd0a86fb37f526a70</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/da/494919d34219bfcb2a8b1ed70519974648f370</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/da/3fa7d3ec3534ccbc945e3ab548a3c4db1a5df4</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/10/7c8860d67a0998610d33fbc1b3fa296b0d5d6e</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/10/3d1df057f44cbe4faa9b66bb989e9ed2e67740</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/10/6065e957e01c6c6b0ac3e25697925d9c90195b</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/10/c6b6c5ef7888b0871515cc833916b6d62fcaa6</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/54/908c135d1f567bb17b6cc3f1b12156d1a57bb7</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/54/de0bd077a91e1cecc6816ee2da44d36d69cd5c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/54/d766e09f33b464fbe9d954b19c8f0cffdfb6cf</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/54/bb99ea635b327b55aa2aadda00109da5390164</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9a/f5c90d2b1f135dee50547396867c9e8135ffb9</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9a/3e90d13e7458330f5586e65de8349bd3a1f05e</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b2/e524bbe3d8a19dc93e7ff98c10abb8a254409f</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b2/4bedb9ab0674c7e451ebaa507466d164effa74</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1b/e9026161c1947753f38ecb363acb953f972ffa</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1b/557881a3a8677ba77e39a0cc362a6f4ba75900</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1b/e9fe0a11433736c5ad800ac66dbfe51486b79f</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1b/f10530e10179712055102a098ec7dfb9f16d60</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/db/199b8af28f593f45fff7f82ea8a1281da15337</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/15/b954bb2d223ee7d77b2e6de27bd556dd235ee7</loc> + <lastmod>2026-06-21</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/15/d053ba53158aa896c1bd4a7eb66bfd7e60d5e4</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/15/947a79fe675a62c67c39b51bdb21c6a74869e1</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/15/2500d45dba9f05b1222043aa21a8800b0709e8</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/15/53e2420dfd00eedb285697b9d7dadc42c4d917</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c2/622ac6143bcf1751d65e4d73b3a4a5c5e0dac8</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c2/195e645617a36f13a6589fc306ed4d797a7938</loc> + <lastmod>2026-06-21</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c2/e8d63fc928ac569ee0a57706db77e91a96f892</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/43/3c0ee3d0ddb8f27641d034168a9f2e0f719815</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a5/88989de12ce6ea731f5816c9ee3ca30575755d</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a5/a677ebc003ea85f4be4d5e62a6753b99af2ecb</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2b/6f27a72176f010fc7bd9f0bef315dbb3da74d1</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2b/752eaef41fde04d9952feb6b1e9457f0d6e515</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b6/7dfd8716da42f68a38809d64933d46a6528876</loc> + <lastmod>2026-06-21</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/04/ea5cb5c37f2b6a065ae77711ead2cf4f524bb2</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/41/9369b9d2d37902406636977ab8f85c7957489e</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/41/98fdef5f59e5e44b472293e7df9937614d205b</loc> + <lastmod>2026-06-21</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/41/f96fb153f5d0ac742c8a6d16fc14dce76da296</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/03/423a214bb64eae442e151396fdc9e75bf56456</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/03/ca8f5d75a24ccbfb57f0e0cc96b9703c2bcc0d</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/25/d9cc8babb8727718a320bbeaecdb5c478c77c8</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/25/b8aef7bb7a707d423f4eac27019ff3bd7ff4b7</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7b/ecb1ccc4a860758bc3f202d138ef8f1a769f4f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7b/3cd84c6b0268e16ab1d3bd143aabd8adc6ff87</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7b/d1ad0214dc771cca428a6bf55df4a9b3eeaa1b</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/67/ea1725de0b0e3a17a1cdbb8321fd3ec8ad3971</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/67/d53db7c24cf70ee74bc3b440a8562c958bdcf9</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/67/2204a1a0154e8232d44fee904580d5acb838fc</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f1/3a8eea99ffb2493f9d846032944c559279023e</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f1/5fb5f8c46a7400d1785dfe5e2036d04a639a2f</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/93/3fe95acb0257c3b92a78d2f92972681132e457</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/93/35ac6b9c2790c470da66fb7d6246e1493d085a</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/93/3bb217855353d1a790bd833e07c3a7d015a0ae</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/01/36bb9d1762f68d5479004e383ed1b0cd84da76</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/98/3bffa010cac7e692f58f26b899355a30134bb9</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/98/505db6207ba7eac67524e771a224862c6c25cb</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/98/abdda39711153b5765b207a0aa848c046b82f5</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7c/01d92774475ec3b36aedd6a840461f03bb0d4e</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7c/a8052a0cd8b2bfd76972a3a45d09a1dced370d</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/16/fe036c4f9259e672fb1bfea03d6c923f9c5e60</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/16/01b4c15a3c7cd55bdc9b90d4766135ec292fcf</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6e/ef16f23cdce44347714b146a0eab384549efe8</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6e/013101fbb7a9226455b31280257fc615d05be0</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6e/8e8151bd56f6fe4d07ffa9fd25d3718cee6f11</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6d/1bca5163c451b4ea64b07c58103685d57e46e9</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6d/aee3bb373cdb155dcc86cd29900286653c8f0f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6d/9ff579768f733eef7b6d5ce3073ac829e0087f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/6d/04d67aa08620aeaa14aabd99fb8d95d8cffb8c</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/12/5dc5d3c901c152b664b092a0b0b68e1900cb10</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b4/d6acfe16a7206a9951b8cf854339dacc66cfe3</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b4/a08f4564839cf0bd653d8e712b074c4def8dd1</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b4/b6e46bcb34375b94b762eb81c8807131fa44b2</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b4/9d673b5656c3b28d5529766245ef8d4447f926</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b4/a93927aac7010f69d93a7132731a20aaa76973</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b4/31647360b486ee9afc3996f62821e1698b314f</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2f/66a78d8a956bee7f8de1f691ad1640b420c98a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2f/f77257950970d05c738a687726a3fc123db3f2</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2f/fd6c4347bd8c05896b8dcc8905a6ec0ebab0bd</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e2/c924e1ba097d3e5a2c4349b983d47366cd6841</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e2/44b93f0a2b8888191db860da57b7010fc9477a</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e2/a2847c5a11a4c01213ef76e7c04dc2d185f549</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e3/c7656fe6fe2e42892da21f477921fd681401aa</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e3/4ab85c023d159a13bd06aac9c9b5bc5ec43079</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e3/024e818ad3a03cf2779195ea3b3d7fbb3ff5cb</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e3/cf80548e9b48434eb2f65b5802d42083fbfbfa</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/08/8d226795ec75b4404b16c895bb22691ec2d779</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/08/472a52f6cc1073f684aa76d4133abb2445d942</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/17/ae069fba2a737f6bd47528bf17da2f95adeda1</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/68/8a35e9cc30931ffa55b25be4ddc441fdab7521</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/68/950737d2d593893b82c93e16166cd74fa040a2</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/68/30f7640ab7bec7ff3c4d6643f22c725f680f69</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/68/15250c53aca6b59169853ecaa29270903847b1</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/68/9f5696bfde486748ffcdb45cb183e66bdb58c2</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/97/51c8ac02425bf86138dacdebc03878b3c72ed7</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/97/7a890ff1b0cbf022f1d1e659955a8fb6a2245c</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/55/885cec3edefcec1e5f8216baa5dfcc1422fd6b</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/55/bb4db1b6b9ae1816e284f2f789f53375ac3bab</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/55/2b9fac6078176038282218e034d6159149646b</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/55/a49333b41c1dff6d13d695ff00de278bacef55</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/60/62f78852b7036c2e7fda7f249f3a07d9764839</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/60/d7b44ebf935ea8969e4c517645fec2acd78223</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/83/12fa52ad1e7c454d3157b408941a718eb94a9c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/83/0d0b25fb5b740a7958eaf3a1397baa0f05736d</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/83/c1f7b1dfd50b385e74253ce4b384fa0b710715</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/77/d31ec3119c1dd5f1f57b923a72a364c8fec75f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/77/d77c6b19a49c0f98ff7d46e6a51490e36a029b</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/77/f39342ce7252c6f96554d6fde9199d2f3532db</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/40/66ad7a961eb85817653dfe2bde5d239dadf5a0</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/40/a57926116410ce548e258d80fd1c76dc2a7470</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4c/569a0efac6e54447252f0d56b8b902af9039ec</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4c/9c320f5a6b48121208980f70f1f4608fe61fa5</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4c/7259be68230b855558619672d2dff22faef281</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4c/ee2741cec886a5d3500b5e16853239a2a6c91a</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4c/020e2a0f00f79864b41996bf379c15f8894f17</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9b/11dedbf66ca4db96482a4641077d5a0df196e6</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9b/c17715eae4ffe456a9bca804138288fe83947f</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9b/72f5c96021e66c5076d44cf91a3bead9d3f762</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9b/28eed12dd4d91ebfd2655c1031241f4f9449d3</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9b/67878258b6bda24f532ab2be10e22acb2f61d9</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/9b/d69269efb4c9bbd1e1072e9d1a70e72f9de05a</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/3c/817aa646108756706a84b020767bdbd8785375</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/3c/97b627e8bca6d56cd8f30e5c373b65a7c660b6</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d1/682eea4fd323fc641e9b89a8ced2fc2d3aa3f0</loc> + <lastmod>2026-06-21</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d1/ea1488266b855728caced6d6b93904dbe0240a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d1/6485ed956c9dee20e4caf9b09fbd259b21b089</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/dc/b3387994bd354537479dc77bb2eeb82800c227</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/dc/bcaf741340564067b35f3d9aae45c73c8647e3</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/dc/2ea6bc91c03367e81c168405b311f11901d119</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e4/176b979e33b2e7c97f22669a2f337c770c820a</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e4/f784cda62bec313b801ef26f2c345a588aa4f4</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/bb/4a9d66aee85b151aa16bc98cba8ac7380391f6</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/bb/64c73199efc3c6d637f6fe2f11d3c1dc758397</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/bb/7bfcf537f98672deb4dacc918f5ffdeef5782e</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/bb/911661f72a8f72538553f872ac1f273eade329</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ee/5e61b6d8c5eb08a9fba7aae0635383632745a1</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ee/d45c30838e8ed86512fb2cae39555aacc54aed</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ee/184b8466a4d5044f806f966d18b2fb5bfdf2ef</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ee/6474170c94adb4263561f7873ca479236531c8</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ee/c0bde6d6385abf8696b37ff985b648ab6e2408</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7f/223d1828789b776c310e779f210fce544da40e</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7f/8555b8297f6fabc5d00944dc3fd8ed4d4e86e3</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7f/18aeeea406f66febe3ab3ee5bc638e962e8c2b</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/35/ae03b164f847a623c9ff96d100b167c53c57b8</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/35/a85410f02a917e94ea36f328f96b8e8775d298</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/35/2b4c71027fe554b9db4012b3b41750e6cb0523</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a9/c0b7505395c6eb292a7591a638573690744c80</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a9/fb27e6636f4eddc9dd3c2c06e59ab62f31ac06</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cc/b829a0cf5e7f2aa69786a5c39a75494bf589d7</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8a/d3a6f265d6875185d3175aa971f3ab05c7404e</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e7/c0903938450893689a0a43c7344713c0be6b52</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/84/25c2b579ffbd07c9ff8ffd19bd0d6a8062e11f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/84/b4a9c08b9ca081b712d3dbb1578526380cb129</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/84/6523f4a236436f1da5ad99d9608b480f7d822d</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/info/commit-graphs/commit-graph-chain</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/info/commit-graphs/graph-ec316f52d946d9dc63947777b289f48757b07fb0.graph</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/info/packs</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ad/364ede2eefaee98d2f374069b51604f7dfd54f</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ad/09258d021f57f45d0a79990bec2a8cf3d22d6a</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ad/2a02d168b2ca5f33503ead089d16b246c16aa7</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ad/c3babed55f417c442d743078c28543bfbb6bdb</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/74/b0dc7b6a1644b62733147c20c238be62682ab6</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/d4/005afc6e7b6bc6d5a7452525f11ddd7fb0bbfd</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/57/21c8e0e57da3c71155881da02b7caf1b9ca623</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/57/2b20a23a54dc044b1d17eca1b555d4c4d5486a</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/57/d0562ff61bd3db5a0efc261aea90352a2c0f21</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/57/a20d6bfe34f137867a764f614ee24abd6d5a8a</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7e/341c29a2e2edd5e8049286419499edfdd2cb68</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7e/bef78eeced2848e6b0bb6eb5f8c8a438643130</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/7e/08c60f03d7a8478ebe8f204c1da93b9d077462</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b9/16d10bea3ac1ca1d088eff1f265838d6e3ff61</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/75/f385ac7ecfef112849233ed37a67d94f647c0a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/75/390fee5336f042f087f24f740ba7b0a1c55542</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/42/caa89eeb8eba0d6045374134b90672dff977eb</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/42/dcc48844dc18fd9037edc6d62e7fb7de060693</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/42/638a23e14ab3a66b06a2ee4ab4bdcfdf7d3bb7</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/42/4a5e228e45f679ec5baa1a9fee2f6d9261c3d3</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/11/eeafb254b489b1603a9c34c78ed3c6270c9e59</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/11/f6efe4f38528129954a29ad0f41f0633c1b36c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/71/9dd56cad315b92fd6a021864bf973983c8053b</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/71/8e66d5301ef7c9310fe160dbc73d39e263a7ab</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/71/4e790fe8517b3b3abf59f9aff3fd5bde2fc851</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/71/0bbe3332985133da7e55c1428d1a40cad511f8</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c7/04d9fa419c1096b0bc13ecdcf9edc7693718c4</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c7/ea9ef2a58d3294de4015d8acee8bbee061ef61</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/39/393c506f0a55c61f123c02c31e18376de247ae</loc> + <lastmod>2026-06-21</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4b/584a3b34127e0c589b5ff436aa7231073dca9e</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/4b/6ce7c2d9589e2406c6fd481c4ab104c963eeec</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fb/1b0a59607bcd51b78bc239114088b3128e404a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fb/167da7aab66e5bb6d74b89abca3be38636a4f8</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fb/b2aab0b4f0920721952e2043272fd8d5ac7e0a</loc> + <lastmod>2026-06-21</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fb/fdccdd1b37208dc509c89971c0ff9cdbfe1a7f</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fb/250719f168b642c3054aa032ce85de7c5e8432</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fb/0aacf3221b6c6bf1126144fd2dc9f30567187a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fb/0f9fc12a7ff2c5552945e886cc9a998013d088</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/88/f63a2ba160385615b7442d4b3e227575986976</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/88/526916bb2d7adc4c3591c7634add1db9e4360c</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/88/6418139bd5ad28bd83a915c23e11954cdc8904</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/58/90d25777ef0d586980dba8ceee19d9f79be3bc</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/58/52435d417f3b645b09aa1ab07232e0f78c8dc2</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/58/0333956739a6a8d3c0e00f80f42e24c6ad4352</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/58/c45a135718dfe90d8c4f55a461254e563dbd0d</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/58/c0ab32017b46783163d3b9e1c70945f35fde98</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/58/234a6cc5f93b027eef03edcc22a9ee885e0b54</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/58/173673fd5e95ffe1ccf922d4cd475ebf05e658</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8d/a47ba7d982f0f5fc7e04acb0e1e2f763db3591</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8d/162e387b4419dc8fdff2c8a4252fc60b0f9a91</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8d/989b9e4049033ac57acfb51cd4f1584466768f</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8d/c1b4b29e286338d1249b4647111d7971ea080a</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1e/2b89458124229e01c7c15e275f0a982653e930</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1d/6732bc76aa22f7b984f67bd832cc136d4f37a1</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/1d/3ecc71fe21e1ac58e5a4e50b9228842becb0ea</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2d/5894bf18a1370c124c0b89883298ac76cf13ed</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2d/0f9b8c8231d36d0ea683f1f6270860b23f5334</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/24/71204f6a7987dc077a2174d21be16419820982</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/24/8353756336a79c3a652c661f89f76c1579df3c</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/24/8f15f067dd4a6adf150966aadd824b2d080b50</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8b/66ffe0c9fb3283c86cf95758cccf2f455ff9fd</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8b/6c017894d7a7fd78652ad730f24ac37ec85633</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8b/92cb522fa0e03da69e9ecce8754eabf3577aee</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8b/4077899b8dd5d23abb5f6f80ab5e5ddef77289</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/8b/8be0f63e0b4fac979b1711ed148bfc6f3943d5</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/37/61d176760704589073815d361e5bdf64da31a2</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/c5/37ffcbc1bebf89d1e37ce2522c21bfc42af5ea</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a6/756dc56a0a799306821fe66623a1622bc6c17e</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a6/73cd1cc1ea168f5825d2674347b63f07815feb</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/61/083c70e4a3e52243ec3541ef6e4c6ad45a0cc1</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/61/7eec09bdb46d0e0e06c02f4787231d73587309</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/61/9879ac0deb4514980e42bf360f91dbe3b1ed6a</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fa/037ca2aa368f502940007875417c8d2bdc69bb</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fa/f9632b549c4b69fc24962b76c464b11d7d439f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fa/85ecdb822152e787b7dad4655e46e3e3a545fd</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/3b/1fd3c1ad60e4b4b6b1a6c44fd9c0d348e17974</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/3b/bc640cc68d8e194cc66b6b34f3b8fb8351c499</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/bc/2f80702b2a38afd869973af3bb1db435c07f22</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/df/6ea7ffaf09e60339db697b5338415cb56f4e43</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/20/dda778df71ad6f0159062d2dd808c7ac11b9f2</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/92/408946fa858c3633ac93239a9f20cbfdc80ec7</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/92/8a708b166cbc283b5cec78bc5cf656bd89def0</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e0/b3e4c47c6698bad07dc465e9dd732ac8234805</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e0/934f1772a668ad44de31199c12991b796f3b2f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e0/3b92f558d97c1a5648868c19a73dbaa17a7bb5</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e0/4c7e3bf1a8d4fe2f4c6864f71c1b2b54ceac22</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e0/7b7ea60277c2a8dca472bc9e0a9908b21ca408</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/e0/ef08b5733bf0566dcd5482f92e053b1795acb1</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ab/fba856c29edd15c19e752f17813c8665413115</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ab/0d02a3aa4ae217a8113a84e540a900e1366ab5</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a4/ca86fd2d6abe9fe38da6f12989c34ef40310e6</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a4/4978a90d41839f3d4752403efe18193d5095f5</loc> + <lastmod>2026-06-30</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a4/39bd90f011681a05f582587593143c74862486</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a4/6e74aa9e0de849413799ae05f727a69d2e0756</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a4/b1e93f55ee6bb7d9a9a5c82cabf2825352e515</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/96/73051907a6affc8d40ddbd3151874971625dcf</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/af/66de7487112c9d6b733c090f1fed5471b93388</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/af/7b1a391951b5c089b248a60fcf8c6299b36337</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f0/392cd66b659df501c85c3a25975188eec30fd0</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f0/80e84d9335c9c6e670b05548dbd472ed15d851</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f0/bbaceb35bfaade2ce90ed5d7c123f5d6f8d7f4</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f0/5255488ce7400e19a9a431aaf771e87b8ad65e</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/f0/9054de2069539621a01a2babfe93bba1235b72</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/85/3d0d340455f06789f43ba0a3d59cb94076f2e4</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/85/6fd30bd9a0004d40840e4aff715405ffa27b9c</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/85/7f8831a85917ec520a4c482fc02ad45b3c0a88</loc> + <lastmod>2026-06-19</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/85/eea0cb4773e5e6f7f23099c0fc406284508537</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ec/35a31bd655377a84bf165e00b7306a1c04978a</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ec/1c48f314b0793f1ecec9e2aa2cd02e6c0b177e</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/ec/8408981b04efe66eba0609cc9617a83e4e8a18</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a8/bf19be5d46d4f24406b1f71fd7f6f725fd6db1</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a8/ed527fa896680d807e57c99cb5320d0d6e3f64</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/a8/1a21351b441a66a93b3c11976fbcd67220fc18</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fc/032a185b03f0cc4bbd0ddd021ced37327701b3</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fc/2f98716e246ced27caf2d7e6c61333cc765c90</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/fc/77ae8e913d1d676ba97abe194094b4d1b24114</loc> + <lastmod>2026-06-22</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b5/a736a053d663becdd89907341a652bfa6f0cb7</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/b5/66127ca2b8e6c40889de134f06ca47ef712faa</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cb/db24ce7137d1c6897fe6024931797f1128297a</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/cb/077363645c5118e527bf418c840ddf3cfe4e72</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2e/9dd27a6f5263d98e60a2de926d0d6d1c45d3f9</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2e/98c5caf9be6fb1cbdab3e960dbafc42af0af3f</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/2e/8010d1771ec85c0eaf115948dba7dfe3e6a7dc</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/73/5529faa98c48879439320a4875cc8cd001e8d8</loc> + <lastmod>2026-06-23</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/objects/73/20a0eae07e227bd32c76cb1d601dbf7aa81ea2</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/COMMIT_EDITMSG</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/logs/HEAD</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/logs/refs/remotes/origin/master</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/logs/refs/heads/master</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/refs/remotes/origin/master</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/refs/heads/master</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/config</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/info/exclude</loc> + <lastmod>2026-05-31</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/.git/info/refs</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-yousuckatprogramming/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-yousuckatprogramming/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-yousuckatprogramming/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-eff/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-eff/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-eff/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-cosmic-voyage/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-cosmic-voyage/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-cosmic-voyage/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-pluralistic/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-pluralistic/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-pluralistic/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-veronica-explains/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-veronica-explains/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-veronica-explains/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-smolweb/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-smolweb/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-smolweb/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gemini-vigrey.com/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gemini-vigrey.com/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gemini-vigrey.com/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-mattos/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-mattos/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-mattos/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-wittamore/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-wittamore/index.html</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-wittamore/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-wittamore/feed</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/news-euronews/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/news-euronews/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/news-euronews/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-adele/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-adele/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-adele/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-lfa/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-lfa/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/gopher-lfa/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-mental-outlaw/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-mental-outlaw/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-mental-outlaw/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/news-guardian-europe/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/news-guardian-europe/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/news-guardian-europe/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-scitechdaily/title</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-scitechdaily/feed</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-bloggings/title</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-bloggings/feed</loc> + <lastmod>2026-07-01</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-naomi-brockwell/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-naomi-brockwell/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/youtube-naomi-brockwell/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-20minutes/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-20minutes/title</loc> + <lastmod>2026-06-20</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-20minutes/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/fediverse-philip/title</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/fediverse-philip/feed</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-petergartner/index.php</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-petergartner/title</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/feeds/http-petergartner/feed</loc> + <lastmod>2026-01-25</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/scripts/SimplePie.compiled.php</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +<url> + <loc>https://wittamore.fr/scripts/getfeeds_http.php</loc> + <lastmod>2026-06-18</lastmod> + <changefreq>weekly</changefreq> +</url> +</urlset> diff --git a/static/140-html-color-names.html b/static/140-html-color-names.html new file mode 100755 index 0000000..50c6dd8 --- /dev/null +++ b/static/140-html-color-names.html @@ -0,0 +1,227 @@ +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/static/index.php">Back</a> + </header> + <main> + <article> +<h2>Html colour names</h2> +<h5> +2023-11-30 +</h5> +<p>As of 2005, all modern, general-use, graphical browsers support this list of X11 color names.</p> +<table> +<tr> + <td style="color:black;background-color: #F0F8FF">AliceBlue</td> + <td style="background-color: #556B2F">DarkOliveGreen</td> + <td style="background-color: #4B0082">Indigo</td> + <td style="background-color: #9370D8">MediumPurple</td> + <td style="background-color: #800080">Purple</td> +</tr> +<tr> + <td style="color:black;background-color: #FAEBD7">AntiqueWhite</td> + <td style="background-color: #FF8C00">DarkOrange</td> + <td style="color:black;background-color: #FFFFF0">Ivory</td> + <td style="background-color: #3CB371">MediumSeaGreen</td> + <td style="background-color: #FF0000">Red</td> +</tr> +<tr> + <td style="color:black;background-color: #00FFFF">Aqua</td> + <td style="background-color: #9932CC">DarkOrchid</td> + <td style="color:black;background-color: #F0E68C">Khaki</td> + <td style="background-color: #7B68EE">MediumSlateBlue</td> + <td style="background-color: #BC8F8F">RosyBrown</td> +</tr> +<tr> + <td style="color:black;background-color: #7FFFD4">AquaMarine</td> + <td style="background-color: #8B0000">DarkRed</td> + <td style="color:black;background-color: #E6E6FA">Lavender</td> + <td style="color:black;background-color: #00FA9A">MediumSpringGreen</td> + <td style="background-color: #4169E1">RoyalBlue</td> +</tr> +<tr> + <td style="color:black;background-color: #F0FFFF">Azure</td> + <td style="background-color: #E9967A">DarkSalmon</td> + <td style="color:black;background-color: #FFF0F5">LavenderBlush</td> + <td style="color:black;background-color: #48D1CC">MediumTurquoise</td> + <td style="background-color: #8B4513">SaddleBrown</td> +</tr> +<tr> + <td style="color:black;background-color: #F5F5DC">Beige</td> + <td style="background-color: #8FBC8F">DarkSeaGreen</td> + <td style="color:black;background-color: #7CFC00">LawnGreen</td> + <td style="background-color: #C71585">MediumVioletRed</td> + <td style="background-color: #FA8072">Salmon</td> +</tr> +<tr> + <td style="color:black;background-color: #FFE4C4">Bisque</td> + <td style="background-color: #483D8B">DarkSlateBlue</td> + <td style="color:black;background-color: #FFFACD">LemonChiffon</td> + <td style="background-color: #191970">MidnightBlue</td> + <td style="background-color: #F4A460">SandyBrown</td> +</tr> +<tr> + <td style="background-color: #000000">Black</td> + <td style="background-color: #2F4F4F">DarkSlateGray</td> + <td style="color:black;background-color: #ADD8E6">LightBlue</td> + <td style="color:black;background-color: #F5FFFA">MintCream</td> + <td style="background-color: #2E8B57">SeaGreen</td> +</tr> +<tr> + <td style="color:black;background-color: #FFEBCD">BlanchedAlmond</td> + <td style="color:black;background-color: #00CED1">DarkTurquoise</td> + <td style="color:black;background-color: #F08080">LightCoral</td> + <td style="color:black;background-color: #FFE4E1">MistyRose</td> + <td style="color:black;background-color: #FFF5EE">SeaShell</td> +</tr> +<tr> + <td style="background-color: #0000FF">Blue</td> + <td style="background-color: #9400D3">DarkViolet</td> + <td style="color:black;background-color: #E0FFFF">LightCyan</td> + <td style="color:black;background-color: #FFE4B5">Moccasin</td> + <td style="background-color: #A0522D">Sienna</td> +</tr> +<tr> + <td style="background-color: #8A2BE2">BlueViolet</td> + <td style="background-color: #FF1493">DeepPink</td> + <td style="color:black;background-color: #FAFAD2">LightGoldenrodYellow</td> + <td style="color:black;background-color: #FFDEAD">NavajoWhite</td> + <td style="color:black;background-color: #C0C0C0">Silver</td> +</tr> +<tr> + <td style="background-color: #A52A2A">Brown</td> + <td style="color:black;background-color: #00BFFF">DeepSkyBlue</td> + <td style="color:black;background-color: #D3D3D3">LightGray</td> + <td style="background-color: #000080">Navy</td> + <td style="color:black;background-color: #87CEEB">SkyBlue</td> +</tr> +<tr> + <td style="color:black;background-color: #DEB887">BurlyWood</td> + <td style="background-color: #696969">DimGray</td> + <td style="color:black;background-color: #90EE90">LightGreen</td> + <td style="color:black;background-color: #FDF5E6">OldLace</td> + <td style="background-color: #6A5ACD">SlateBlue</td> +</tr> +<tr> + <td style="background-color: #5F9EA0">CadetBlue</td> + <td style="background-color: #1E90FF">DodgerBlue</td> + <td style="color:black;background-color: #FFB6C1">LightPink</td> + <td style="background-color: #808000">Olive</td> + <td style="background-color: #708090">SlateGray</td> +</tr> +<tr> + <td style="color:black;background-color: #7FFF00">Chartreuse</td> + <td style="background-color: #B22222">FireBrick</td> + <td style="color:black;background-color: #FFA07A">LightSalmon</td> + <td style="background-color: #688E23">OliveDrab</td> + <td style="color:black;background-color: #FFFAFA">Snow</td> +</tr> +<tr> + <td style="background-color: #D2691E">Chocolate</td> + <td style="color:black;background-color: #FFFAF0">FloralWhite</td> + <td style="color:black;background-color: #20B2AA">LightSeaGreen</td> + <td style="background-color: #FFA500">Orange</td> + <td style="color:black;background-color: #00FF7F">SpringGreen</td> +</tr> +<tr> + <td style="background-color: #FF7F50">Coral</td> + <td style="background-color: #228B22">ForestGreen</td> + <td style="color:black;background-color: #87CEFA">LightSkyBlue</td> + <td style="background-color: #FF4500">OrangeRed</td> + <td style="background-color: #4682B4">SteelBlue</td> +</tr> +<tr> + <td style="background-color: #6495ED">CornFlowerBlue</td> + <td style="background-color: #FF00FF">Fuchsia</td> + <td style="color:black;background-color: #778899">LightSlateGray</td> + <td style="color:black;background-color: #DA70D6">Orchid</td> + <td style="color:black;background-color: #D2B48C">Tan</td> +</tr> +<tr> + <td style="color:black;background-color: #FFF8DC">Cornsilk</td> + <td style="color:black;background-color: #DCDCDC">Gainsboro</td> + <td style="color:black;background-color: #B0C4DE">LightSteelBlue</td> + <td style="color:black;background-color: #EEE8AA">PaleGoldenRod</td> + <td style="background-color: #008080">Teal</td> +</tr> +<tr> + <td style="background-color: #DC143C">Crimson</td> + <td style="color:black;background-color: #F8F8FF">GhostWhite</td> + <td style="color:black;background-color: #FFFFE0">LightYellow</td> + <td style="color:black;background-color: #98FB98">PaleGreen</td> + <td style="color:black;background-color: #D8BFD8">Thistle</td> +</tr> +<tr> + <td style="color:black;background-color: #00FFFF">Cyan</td> + <td style="color:black;background-color: #FFD700">Gold</td> + <td style="color:black;background-color: #00FF00">Lime</td> + <td style="color:black;background-color: #AFEEEE">PaleTurquoise</td> + <td style="background-color: #FF6347">Tomato</td> +</tr> +<tr> + <td style="background-color: #00008B">DarkBlue</td> + <td style="background-color: #DAA520">GoldenRod</td> + <td style="color:black;background-color: #32CD32">LimeGreen</td> + <td style="background-color: #db7093">PaleVioletRed</td> + <td style="color:black;background-color: #40E0D0">Turquoise</td> +</tr> +<tr> + <td style="background-color: #008B8B">DarkCyan</td> + <td style="background-color: #808080">Gray</td> + <td style="color:black;background-color: #FAF0E6">Linen</td> + <td style="color:black;background-color: #FFEFD5">PapayaWhip</td> + <td style="background-color: #EE82EE">Violet</td> +</tr> +<tr> + <td style="background-color: #B8860B">DarkGoldenRod</td> + <td style="background-color: #008000">Green</td> + <td style="background-color: #FF00FF">Magenta</td> + <td style="color:black;background-color: #FFDAB9">PeachPuff</td> + <td style="color:black;background-color: #F5DEB3">Wheat</td> +</tr> +<tr> + <td style="background-color: #A9A9A9">DarkGray</td> + <td style="color:black;background-color: #ADFF2F">GreenYellow</td> + <td style="background-color: #800000">Maroon</td> + <td style="background-color: #CD853F">Peru</td> + <td style="color:black;background-color: #FFFFFF">White</td> +</tr> +<tr> + <td style="background-color: #006400">DarkGreen</td> + <td style="color:black;background-color: #F0FFF0">HoneyDew</td> + <td style="color:black;background-color: #66CDAA">MediumAquaMarine</td> + <td style="color:black;background-color: #FFC0CB">Pink</td> + <td style="color:black;background-color: #F5F5F5">WhiteSmoke</td> +</tr> +<tr> + <td style="color:black;background-color: #BDB76B">DarkKhaki</td> + <td style="background-color: #FF69B4">HotPink</td> + <td style="background-color: #0000CD">MediumBlue</td> + <td style="color:black;background-color: #DDA0DD">Plum</td> + <td style="color:black;background-color: #FFFF00">Yellow</td> +</tr> +<tr> + <td style="background-color: #8B008B">DarkMagenta</td> + <td style="background-color: #CD5C5C">IndianRed</td> + <td style="background-color: #BA55D3">MediumOrchid</td> + <td style="color:black;background-color: #B0E0E6">PowderBlue</td> + <td style="color:black;background-color: #9ACD32">YellowGreen</td> +</tr> +</table> +<p> </p> +</article> +<footer + <p>§</p> +</footer> +</main> + </body> +</html> diff --git a/static/Interesting-links.html b/static/Interesting-links.html new file mode 100755 index 0000000..4198fde --- /dev/null +++ b/static/Interesting-links.html @@ -0,0 +1,114 @@ +<!doctype html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/static/index.php">Back</a> + </header> + <main> + <article> + <h2>Interesting Links</h2> + <h4>(maybe be for some)</h4> + <h3>Search engines</h3> + <ul> + <li class="articlelink"> + <a href="https://search.marginalia.nu/">Marginalia Search Engine</a> + </li> + <li class="articlelink"> + <a href="https://wiby.org/">Wiby Search Engine</a> + </li> + </ul> + <h3>Hosting</h3> + <ul> + <li class="articlelink"> + <a href="https://www.hotglue.me/">Hotglue web publishing</a> + </li> + <li class="articlelink"> + <a href="https://web1.0hosting.net/">Web 1.0 Hosting</a> + </li> + </ul> + <h3>Tools</h3> + <ul> + <li class="articlelink"> + <a href="https://bash-prompt-generator.org/" + >Bash prompt generator</a + > + </li> + </ul> + <h3>Stuff</h3> + <ul> + <li class="articlelink"> + <a href="https://lucio.albenga.es" + >Lucio's website: UNIX, programming, retro computing, electronics, + and more</a + > + </li> + <li class="articlelink"> + <a href="https://int10h.org/oldschool-pc-fonts/" + >Old school PC fonts</a + > + </li> + <li class="articlelink"> + <a href="https://neilzone.co.uk/">Neil's personal Blog</a> + </li> + <li class="articlelink"><a href="https://b3ta.com/">B3ta</a></li> + <li class="articlelink"> + <a href="https://github.com/toolleeo/awesome-cli-apps-in-a-csv" + >Awesome cli apps</a + > + </li> + </ul> + <h3>News</h3> + <ul> + <li class="articlelink"> + <a href="https://news.ycombinator.com/">Hacker News</a> + </li> + <li class="articlelink"> + <a href="https://brutalist.report/">Brutalist report</a> + </li> + <li class="articlelink"><a href="https://lobste.rs/">Lobsters</a></li> + <li class="articlelink"> + <a href="https://lwn.net/">Linux Weekly News</a> + </li> + <li class="articlelink"> + <a href="https://ctrl-c.club/~loghead//ctrl-zine.html">Ctrl-zine</a> + </li> + </ul> + <h3>Other lists</h3> + <ul> + <li class="articlelink"> + <a href="https://greycoder.com/a-list-of-text-only-new-sites/" + >Greycoder's list</a + > + </li> + <li class="articlelink"> + <a href="http://biglist.terraaeon.com/" + >Big list of personal web sites</a + > + </li> + <li class="articlelink"> + <a href="https://sjmulder.nl/en/textonly.html">SJ Mulder's list</a> + </li> + <li class="articlelink"> + <a + href="https://github.com/mayfrost/guides/blob/master/ALTERNATIVES.md" + >Mayfrost Alternatives</a + > + </li> + </ul> + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/static/Mention-Legal.html b/static/Mention-Legal.html new file mode 100755 index 0000000..16549c5 --- /dev/null +++ b/static/Mention-Legal.html @@ -0,0 +1,63 @@ +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/static/index.php">Back</a> + </header> + <main> + <article< +<h2>GDPR</h2> +<h4>[English]</h4> +<p>Wittamore.fr is a personal blog published in a non-professional capacity. +It does not use cookies, does not track visitors and does not store their e-mails or information +with the exception of temporary server logs for debugging purposes. Its content is free of rights. +</p> +<small> +<p>Details of the site host: +<br>OVH SAS, RCS Lille Métropole 424 761 419 00045 +<br>Head office: 2 rue Kellermann, 59100 Roubaix, France.</p> +</small> +<p> While I endeavour to keep the information up to date and correct, +I make no representations or warranties of any kind, +express or implied, about the completeness, accuracy, reliability, suitability +or availability of the information contained on the website for any purpose. +</p> +<p>All claims should be addressed to philip [at] wittamore.fr</p> +<hr> +<h4>[Français]</h4> +<p> +Wittamore.fr est un blog personel édité à titre non professionnel. +Il n'utilise pas de cookies, ne traque pas les visiteurs et ne stocke pas +leurs d'e-mails ni informations à l'exception des journaux temporaires du serveur à des fins de débogage. +Son contenu est libre des droits. +</p> + +<small> +<p>Coordonnées de l’hébergeur du site : +<br>OVH SAS, RCS Lille Métropole 424 761 419 00045 +<br>Siège social : 2 rue Kellermann, 59100 Roubaix, France.</p> +</small> + +<p> +Bien que je m'efforce de maintenir les informations à jour et correctes, +je ne fais aucune déclaration ni garantie de quelque nature que ce soit, +expresse ou implicite, concernant l'exhaustivité, l'exactitude, la fiabilité, la pertinence +ou la disponibilité des informations contenus sur le site web à quelque fin que ce soit. +</p> +<p>Toute réclamation doit être adressée à philip [arobase] wittamore.fr</p> + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/static/Neomutt.html b/static/Neomutt.html new file mode 100755 index 0000000..c5138b3 --- /dev/null +++ b/static/Neomutt.html @@ -0,0 +1,95 @@ +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/static/index.php">Back</a> + </header> + <main> + <article> +<h2>Neomutt</h2> +<h5>2021-03-20</h5> +<p>Neomutt is an updated version of Mutt, a text based email client.</p> +<p><a href="https://neomutt.org/" target="_blank">Site de Neomutt</a></p> +<img src="/images/blog/neomutt-screenshot.png"> + +<h4>Configuration files</h4> +<ul> +<li class="articlelink"><a href="/txt/neomuttrc.txt">neomuttrc</a></li> +<li class="articlelink"><a href="/txt/account_philip.txt">account</a></li> +<li class="articlelink"><a href="/txt/venelles.txt">theme</a></li> +<li class="articlelink"><a href="/txt/mailcap.txt">mailcap</a></li> +</ul> + +<ul> +<li class="articlelink"><a href="/txt/display_filter.txt">Display filter</a></li> +<li class="articlelink"><a href="/txt/get-mailboxes.txt">List mailboxes</a></li> +<li class="articlelink"><a href="/txt/translate.txt">Translate emails</a></li> +<li class="articlelink"><a href="/txt/mutt-trim.txt">Smarten up emails before replying</a></li> +<li class="articlelink"><a href="/txt/print_unicode.txt">Printing emails in unicode</a></li> +</ul> + +<h4>External applications</h4> + +<ul> +<li class="articlelink"><a href="https://github.com/baruchel/txt2pdf">txt2pdf</a> Convert text to PDF with Unicode</li> +<li class="articlelink"><a href="https://isync.sourceforge.io/">mbsync</a> I use mbsync to download emails</li> +<li class="articlelink"><a href="https://marlam.de/msmtp/">msmtp</a> I use msmtp to send emails</li> +<li class="articlelink"><a href="https://sr.ht/~bptato/chawan/">Chawan</a> I use Chawan in mailcap to convert html emails</li> +<li class="articlelink"><a href="https://gnupg.org/">GPG</a> signature and encryption</li> +<li class="articlelink"><a href="https://www.geeksforgeeks.org/deepl-command-line-language-translator-tool-for-linux/">DeepL-cli</a> for translating</li> +<li class="articlelink"><a href="https://github.com/soimort/translate-shell">Translate Shell</a> for translating</li> +<li class="articlelink"><a href="https://github.com/firecat53/urlscan">Urlscan</a> manage links in emails</li> +<li class="articlelink"><a href="https://notmuchmail.org/">Notmuch</a> Notmuch email system for searching (I don't use it)</li> +<li class="articlelink"><a href="https://github.com/lefcha/imapfilter">Imapfilter</a> tool for filtering mail</li> +</ul> + +<h4>Personal Notes</h4> + +<p>To enable verification of the GPG signature from the keyservers when +reading an e-mail, make sure that the ~/.gnupg/gpg.conf file contains these lines:</p> +<code>keyserver hkps://hkps.pool.sks-keyservers.net +keyserver-options auto-key-retrieve +</code> + +<p>I use msmtp to send e-mails from my Arch linux laptop. In msmtprc, +</p> +<code>tls_trust_file /etc/ssl/certs/ca-certificates.crt</code> +<p>doesn't work for me, instead I use</p> +<code>tls_fingerprint</code></pre> +<p>To obtain the fingerprint :</p> +<code>msmtp --serverinfo --tls --tls-certcheck=off --host=host.domain.tld + --port=587 | egrep -o "([0-9A-Za-z]{2}:){31}[0-9A-Za-z]{2}" +</code> +<p>I have a cron job that updates the fingerprint regularily.</p> +<code>#!/bin/bash +# update tls_fingerprint entry in ~/.msmtprc + +cd $HOME + +# get the server fingerprint +KEY=$(msmtp --serverinfo --tls --tls-certcheck=off --host=mail.domain.tld --port=587 | grep -E -o "([0-9A-Za-z]{2}:){31}[0-9A-Za-z]{2}") + +# replace tls_fingerprint line in .msmtprc +sed -i "s/^tls_fingerprint.*/tls_fingerprint $KEY/g" .msmtprc +</code> + +<p> +I use Chawan (cha) in my mailcap to convert html mail to text, otherwise xdg-desktop for the default applications +(see mailcap above) +</p> + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/static/about.html b/static/about.html new file mode 100755 index 0000000..2e07d6f --- /dev/null +++ b/static/about.html @@ -0,0 +1,98 @@ +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>Bloggings</title> + </head> + <body> + <main> + <header> + <h1>Bloggings</h1> + <a href="/static/index.php">Back</a> + </header> + <article> + <h2>About</h2> + <p><img src="/images/shadocks-pumping-300.gif"></p> + <h3>A French Englishman</h3> + <p> + <i>"Spends a lot of time looking out of the window" - school reports</i> + </p> + <p> + I first arrived in France from England in 1975 with a hitchhiker's + rucksack. Barely 18, I wanted to get out of my bubble and see the world, + and France held my breath. The wide open spaces, the Massif Central, the + beaches of Corsica. I realised that I could live differently. (<a + href="/sticky/summer-of-75.html" + ><i>read summer of '75</i></a + >) <br><br>Many years and many lives later, I became a proud French + citizen and inhabitant of Brittany. + </p> + <h3>Computers and stuff</h3> + <p> + My first computer was a Sinclair ZX81, although I used a Burroughs TC500 + terminal in 1974 at Barclays.<br><br>In the 80s, I wrote a Morse code + decoder in Locomotive basic on an Amstrad CPC464 for my shortwave radio + and read clear text messages from the French Navy in Toulon. I also built + billing spreadsheets in Multiplan on an IBM PC for a laundry serving + regional hospitals.<br><br>In the early 90s, I discovered Linux with a + copy of MNIS Linux because I didn't want to pay for Windows 95. Then came + the discovery of the Internet with a modem! I cobbled together an intranet + at work using Minitels and home-made electronics, but Windows/Netware was + preferred by my employer. Later the cost of Netware led me to replace + Netware with Mars-nwe, a Netware clone for Linux with dual boot Windows + 3.1/Linux clients. I also wrote a + <a href="https://wittamore.fr/articles/2023/windowmaker-in-1998.html" + >luxury goods label printing app</a + > + in Applixware 4.41.<br><br>I wrote my first website in 1994, and in + 1999, I became both system administrator and support technician for the + French branch of an American software company, where I encountered Unix + AIX, IRIX, Solaris and HP-UX. Two years later, I was appointed sales + manager in France, then in Europe. A highly unlikely combination of + technician and sales, but one that worked well. + </p> + <p></p> + <p> + <img + src="/images/shop.jpg" + alt="" + width="200" + ><br><br> + In the meantime, I opened my own computer repair shop, wrote + a licence management application, a web CRM application, a number of + websites, and a few web applications. + <br><br> + Then I retired. + <br><br> + I'd been working in IT for 35 years, so I + turned to woodworking for a bit of creativity. I also built my own tools + such as a lathe and bandsaw. The advantage of woodworking over IT is that + you obtain a finished object that isn't obsolete after 5 minutes, and can + last for... well, longer than me. + </p> + <p> + <img + src="/images/bowl.jpg" + alt="Birch bowl with pyrograving" + width="200" + > + </p> + <p> + <a + href="https://www.lairdubois.fr/@philipw" + target="_blank" + rel="noopener" + >See my page at L'Air du Bois</a + > + </p> + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/static/index.php b/static/index.php new file mode 100755 index 0000000..19b2e8b --- /dev/null +++ b/static/index.php @@ -0,0 +1,82 @@ +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>Bloggings</title> +</head> +<body> + <header> + <h1>Bloggings</h1> + <nav> + <p><a href="/index.php">Blog</a> | <b>Static</b> | <a + href="/search.php">Search</a> | <a + href="https://git.wittamore.fr" target="_blank" title="Git">Git</a> | <a + href="https://social.wittamore.fr/philip" target="_blank" title="Fediverse">Fedi</a> | <a + href="/feeds/index.php" target="_blank" title="Feeds">Feeds</a> | <a + href="/rss.xml">RSS</a></p> + </nav> + <div class="banner"> + <img src="/images/me.jpg" alt="logo"> + <p>Comments: <a href="https://social.wittamore.fr/philip">@philip</a><br> + Contact: <a href="/images/deltachatqr.jpg">Deltachat</a> + </p> + </div> + </header> + <main> + <article> + <h2>Static pages</h2> + <!-- list start --> +<?php +// iterate through years +$items = []; +echo "<ul>"; +// iterate through files +$i = 0; +$thisdir = new DirectoryIterator("./"); +foreach ($thisdir as $file) { + if ((!$file->isDot()) && (!$file->isDir()) && ($file!="index.php")) { + $i++; + $content = file_get_contents($file->getPathname()); + preg_match("/<h5>(.*?)<\/h5>/s", $content, $date); + $filedate = date("Y-m-d"); + + if (isset($date[1]) && strtotime($date[1])) { + $filedate = date("Y-m-d", strtotime($date[1])); + } + + preg_match("/<h2>(.*?)<\/h2>/s", $content, $titre); + $title = "?"; + + if (isset($titre[1])) { + $title = ucfirst($titre[1]); + } + $items[$i][0] = $i; + $items[$i][1] = $file->getPathname(); + $items[$i][2] = $title; + } +} +$values = array_column($items, 2); +array_multisort($values, SORT_ASC, $items); + +$i = 0; +while ($i < count($items)) { + echo '<li class="articlelink">' . + '<a href="' . + $items[$i][1] . + '">' . + ucfirst($items[$i][2]) . + "</a></li>"; + $i++; +} +echo "</ul>"; +?> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/static/le-mene.html b/static/le-mene.html new file mode 100755 index 0000000..0d6c1a8 --- /dev/null +++ b/static/le-mene.html @@ -0,0 +1,181 @@ +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/static/index.php">Back</a> + </header> + <main> + <article> +<h2>Le Mené</h2> + +<h4>Actualités</h4> + +<ul> +<li class="articlelink"><a href="https://www.ouest-france.fr/bretagne/le-mene-22330/">Ouest France - Le Mené</a></li> +<li class="articlelink"><a href="https://www.letelegramme.fr/cotes-d-armor/le-mene/">Le Télégramme - Le Mené</a></li> +<li class="articlelink"><a href="https://actu.fr/bretagne/le-mene_22046">Courrier Independante - Le Mené</a></li> +</ul> + +<h4>Les Ondes</h4> +<ul> +<li class="articlelink"><a href="https://www.evasionfm.com/">Radio Evasion</a></li> +<li class="articlelink"><a href="https://www.frequence-radio.org/frequences-cotes-d-armor.html">Radios Côtes d'Armor</a></li> +<li class="articlelink"><a href="https://www.tvr.bzh/direct">TVR</a></li> +</ul> + +<h4>Cartes</h4> + +<ul> +<li class="articlelink"><a href="https://www.openstreetmap.org/#map=12/48.2704/-2.5142">Carte Open Street Map</a> (vous pouvez l'améliorer)</li> +<li class="articlelink"><a href="https://www.geoportail.gouv.fr/plan/22046/le-mene">Géoportail - Le Mené</a></li> +<li class="articlelink"><a href="https://www.visorando.com/randonnee-bretagne-2.html">Randonées</a></li> +<li class="articlelink"><a href="https://www.europe.bzh/">L'Europe en Bretagne</a></li> +<li class="articlelink"><a href="https://www.google.com/maps/d/u/0/viewer?msa=0&mid=1VFvpd2QwySLwHVj9IBs6wwCFfto&ll=48.217429949274496%2C-2.682589550000003&z=10">Carte des produits locaux</a></li> +</ul> + +<h4>Flore et Faune</h4> +<ul> +<li class="articlelink"><a href="http://gmb.bzh/">Groupement Mammalogique de Brétagne</a></li> +<li class="articlelink"><a href="https://www.vivarmor.fr">VivArmor Nature</a></li> +<li class="articlelink"><a href="https://www.faune-bretagne.org/">Faune Bretagne</a></li> +<li class="articlelink"><a href="https://inpn.mnhn.fr/collTerr/commune/choix/22046">Inventaire Patrimoine Naturel</a></li> +<li class="articlelink"><a href="https://www.bretagne-vivante.org/">Bretagne Vivante</a></li> + +</ul> + +<h4>Ressources</h4> +<ul> +<li class="articlelink"><a href="https://www.services.eaufrance.fr/donnees/commune/22046">Observertoire National Eau et Assainissement</a></li> +<li class="articlelink"><a href="https://lagrangeauxabeille8.wixsite.com/website">La Grange Aux Abeilles</a></li> +</ul> + +<h4>Tourisme</h4> +<ul> +<li class="articlelink"><a href="https://www.facebook.com/cotesdarmor.tourisme">Côtes d'Armor Tourisme</a></li> +<li class="articlelink"><a href="https://www.tourismebretagne.com/">Tourisme Bretagne</a></li> +<li class="articlelink"><a href="https://www.mene.fr/decouvrir-le-mene/le-tourisme/">Sentiers de balade</a></li> +<li class="articlelink"><a href="https://www.armortv.fr/">ArmorTV</a></li> +</ul> + +<h4>Histoire</h4> + +<h5>Le Mené</h5> +<ul> +<li class="articlelink"><a href="https://www.inrap.fr/recherche?search_api_views_fulltext=c%C3%B4tes+d%27armor">Archéologie en Mené</li> +<li class="articlelink"><a href="https://histosm.org/#12/-2.42558/48.28776/0/">HistOSM</a> Objets historiques de OpenStreetMap +<li class="articlelink"><a href="https://www.megalithic.co.uk/leaflet_megalith_map.php?country=6">Les megalithes de France</a> Carte interactive</li> +<li class="articlelink"><a href="http://voies-romaines-bretagne.com/vrom2/corseul-vannes.html">Route Romaine de Corseul à Vannes</a></li> +<li class="articlelink"><a href="https://www.geoportail.gouv.fr/carte?c=-2.4987268203755586,48.2813754082255&z=12&l0=GEOLOGY.GEOLOGY::EXTERNAL:OGC:EXTERNALWMS(1)&permalink=yes">Carte Géologique du Mené</li> +<li class="articlelink"><a href="https://renass.unistra.fr/en/zones/france/">Les derniers évènements sismiques survenus en France</a></li> +<li class="articlelink"><a href="https://fr.wikipedia.org/wiki/Monts_du_Men%C3%A9">Les Monts de Mené</a></li> +<li class="articlelink"><a href="https://infoterre.brgm.fr/">Infoterre</a></li> +<li class="articlelink"><a href="http://sigesbre.brgm.fr/Histoire-geologique-de-la-Bretagne-59.html">Histoire géologique de la Bretagne</a></li> +<li class="articlelink"><a href="https://sallevirtuelle.cotesdarmor.fr/AD/cnx/commune.aspx">Cadastres anciennes</a></li> +</ul> + +<h5>Langourla</h5> +<ul> +<li class="articlelink"><a href="https://fr.wikipedia.org/wiki/Langourla">Wikipedia</a></li> +<li class="articlelink"><a href="/images/histoire/cour-de-queneleue.png">Cour de Queneleue.png (préhistoire)</a></li> +<li class="articlelink"><a href="/images/histoire/vieux-chatet-de-coelan.png">Les vieux Châtets (préhistoire)</a></li> +<li class="articlelink"><a href="/images/histoire/bas_fournaux.jpg">Exemple d'extraction de fer à Langourla</a></li> +<li class="articlelink"><a href="/images/histoire/haches-langourla.png">Haches du Maçonnais</a></li> +<li class="articlelink"><a href="/images/histoire/cocantine.png">La Cocantine (vestiges romains)</a></li> +<li class="articlelink"><a href="/images/histoire/eglise_saint_eutrope.jpg">L'ancienne église de Saint Eutrope</a></li> +<li class="articlelink"><a href="/images/histoire/bourg-1824.png">Le bourg en 1824</a></li> +<li class="articlelink"><a href="/images/histoire/delivrance-cartes-alimentaires-1941.webp">Cartes alimentaires 1941</a></li> +<li class="articlelink"><a href="/images/histoire/arrete-confitures-1942.webp">Distribution confitures 1942</a></li> +<li class="articlelink"><a href="/files/langourla.pdf">Carte de Langourla</a> Fichier PDF</li> +<li class="articlelink"><a href="https://www.bretagneweb.com/photos-22/22-langourla.htm">Bretagneweb</a></li> +<li class="articlelink"><a href="/images/mene/langourla/cartes-postales">Cartes postales</a></li> +<li class="articlelink"><a href="/images/mene/langourla/photos/index.php">Photos</a></li> +</ul> + +<h5>Saint Jacut du Mené</h5> +<ul> +<li class="articlelink"><a href="https://fr.wikipedia.org/wiki/Saint-Jacut-du-Men%C3%A9">Wikipedia</a></li> +<li class="articlelink"><a +<li class="articlelink"><a href="/images/histoire/1740.jpg">Carte de 1740</a></li> +<li class="articlelink"><a href="/images/histoire/1866.jpg">Carte de 1866</a></li> +<li class="articlelink"><a href="/images/histoire/chateau-de-la-couaille.jpg">Chateau de la couaille (age de fer)</a> +| <a href="https://www.openstreetmap.org/query?lat=48.27997&lon=-2.48093#map=16/48.2814/-2.4803">OSM</a></li> +<li class="articlelink"><a href="/images/histoire/Allee-couverte-la-rocher.png">Allée couverte la Rocher (BNF)</li> +<li class="articlelink"><a href="/images/histoire/Menhir-de-la-Touche-article.png">Menhir de la Touche (BNF)</a></li> +<li class="articlelink"><a href="https://www.bretagneweb.com/photos-22/22-saintjacutdumene.htm">Bretagneweb</a></li> +<li class="articlelink"><a href="/images/mene/saint-jacut-du-mene/cartes-postales/index.php">Cartes postales</a></li> +<li class="articlelink"><a href="/images/mene/saint-jacut-du-mene/photos/index.php">Photos</a></li> +</ul> + +<h5>Collinée</h5> +<ul> +<li class="articlelink"><a href="https://fr.wikipedia.org/wiki/Collin%C3%A9e">Wikipedia</a></li> +<li class="articlelink"><a href="https://gallica.bnf.fr/ark:/12148/bpt6k56069257/f15.item.r=collin%C3%A9e">Gallica</a> références Bulletin Société préhistorique</li> +<li class="articlelink"><a href="https://www.bretagneweb.com/photos-22/22-collinee.htm">Bretagneweb</a></li> +</ul> + +<h5>Plessala</h5> +<ul> +<li class="articlelink"><a href="https://fr.wikipedia.org/wiki/Plessala">Wikipedia</a></li> +<li class="articlelink"><a href="http://archeologie.canalblog.com/archives/2012/12/02/25722234.html">Les enceintes à fossés multiples</a></li> +<li class="articlelink"><a href="https://gallica.bnf.fr/ark:/12148/bpt6k58028610/f55.image.r=Plessala?rk=150215;2">Habitations Gallo Romaines</a></li> +<li class="articlelink"><a href="https://gallica.bnf.fr/ark:/12148/bpt6k5687891d/f520.image.r=Plessala?rk=171674;4">Chateaux</a></li> +<li class="articlelink"><a href="https://gallica.bnf.fr/ark:/12148/bpt6k58028892/f111.image.r=Plessala%20archaeology?rk=42918;4">Mottes de l'age de fer</a></li> +<li class="articlelink"><a href="https://www.delcampe.net/fr/collections/search?categories%5B%5D=713&search_mode=all&excluded_terms=&term=plessala">Cartes Postales</a></li> +<li class="articlelink"><a href="https://www.bretagneweb.com/photos-22/22-plessala.htm">Bretagneweb</a></li> +</ul> + +<h5>Le Gouray</h5> +<ul> +<li class="articlelink"><a href="https://fr.wikipedia.org/wiki/Le_Gouray">Wikipedia</a></li> +<li class="articlelink"><a href="https://fr.wikipedia.org/wiki/All%C3%A9e_couverte_des_Meurtiaux">Allée couverte des Meurtiaux</a></li> +<li class="articlelink"><a href="https://fr.wikipedia.org/wiki/Passerelles_du_Gouray">Passerelles du Gouray</a></li> +<li class="articlelink"><a href="https://www.bretagneweb.com/photos-22/22-legouray.htm">Bretagneweb</a></li> +</ul> + +<h5>Saint Gilles du Mené</h5> +<ul> +<li class="articlelink"><a href="https://fr.wikipedia.org/wiki/Saint-Gilles-du-Men%C3%A9">Wikipedia</a></li> +<li class="articlelink"><a href="https://www.bretagneweb.com/photos-22/22-saintgillesdumene.htm">Bretagneweb</a></li> +</ul> + +<h5>Saint Gouéno</h5> +<ul> +<li class="articlelink"><a href="https://fr.wikipedia.org/wiki/Saint-Gou%C3%A9no">Wikipedia</a></li> +<li class="articlelink"><a href="https://www.bretagneweb.com/photos-22/22-saintgoueno.htm">Bretagneweb</a></li> +</ul> + +<h4>Anciennes logos des communes</h4> + +<ul> +<li class="articlelink"><a href="/images/mene/logos/index.php">Gallery</a></li> +</ul> + +<h4 id="Bulletins">Bulletins</h4> + +<p>Quelques bulletins que j'ai mise en page lorsque j'étais conseilleur de la village +de Langourla, Côtes d'armor.</p> + +<ul> +<li class="articlelink"><a href="/files/bulletins/bulletin_052014_006_WEB.pdf">Mai 2014</a></li> +<li class="articlelink"><a href="/files/bulletins/bulletin_092014_01_WEB.pdf">Septembre 2014</a></li> +<li class="articlelink"><a href="/files/bulletins/bulletin_122014_WEB.pdf">Décembre 2014</a></li> +<li class="articlelink"><a href="/files/bulletins/bulletin_032015_WEB.pdf">Mars 2015</a></li> +<li class="articlelink"><a href="/files/bulletins/bulletin_062015_web.pdf">Juin 2015</a></li> +<li class="articlelink"><a href="/files/bulletins/bulletin_092014_01_WEB.pdf">Septembre 2015</a></li> +</ul> + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/static/summer-of-75.html b/static/summer-of-75.html new file mode 100755 index 0000000..92799e6 --- /dev/null +++ b/static/summer-of-75.html @@ -0,0 +1,50 @@ +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/static/index.php">Back</a> + </header> + <main> + <article> + <h2>Summer of ’75</h2> + <h5>22-06-2023</h5> + <img src="/images/corse-1975.jpg" alt="beach in corsica 1975"> + <p>The white ferry crossing the channel seemed ancient, with a single passenger deck that was mostly a bar. Was it a converted fishing boat? An Isle of Wight ferry? In any case it was certainly unlike the sleek cross channel ships of today. It felt like going to Mars in a bucket.</p> + <p>I was 18. Tall and skinny with wild hair, in a t-shirt and shorts and a blue backpack that my parents had bought me. I’d read “Dove”, by Robin Lee Graham, the story of a 16 year old who left California to sail around the world alone. At the same age I left school to work as bank clerk, but I dreamed of seeing the world and living differently. Robin’s story scratched around in my brain along with other books. I had already hitchhiked to Hadrians wall, to a hippy medieval festival, to the fossil cliffs in Devon, and South Wales.</p> + <p>My first impression of France was of large roads and big fields, a wide open horizon. I felt liberated, everything seemed brighter and more promising than in England which at that time was in an economical marasmus. I remember elm-lined roads in the north, passing under the Eiffel Tower, and my rear end still recalls the trip in a 2CV from Lyon to Orange, with a cortege of hairy students. Eventually a professional boxer in a black BMW dropped me off at the Marseilles ferry port. I paid for the overnight ferry to Corsica with my last francs, 10 francs perhaps.</p> + <p>I could smell Corsica before landing, as the vegetation gave a distinctive perfume to the air. I quickly ran out of money, but rather than being a show stopper it was just another problem to solve. After a miserable mosquito ridden night in a field near Bonifacio, and a beautiful one in a pine forest with chips of tree bark on the ground, my last lunch was some peaches given to me by a lady on a market stall in Bastia. However I did catch a sign that said something about an employment agency, so I washed my t-shirt in a nearby fountain, let it dry a bit and walked hopefully into the building. The man there immediately found me a job as a dishwasher in a restaurant/pizzeria near Alistro, so I hitchhiked down and was working the same evening.</p> + <p>The job included room and board in a small individual hut. In the morning after a copious breakfast I followed everyone to the beach, which I discovered to be naturist. After a day I took my shorts off like everyone else, it’s surprising how quickly bare skin becomes mundane.</p> + <p>I met some friendly Germans on holiday there in a nearby naturist camp. One of them had a yellow BMW with an open roof, her father had something to do with Coca Cola and seemed to be relatively wealthy. Today I believe she was interested in me, but at that time I was oblivious. We played card games where the loser had to drink a shot of apple alcohol. Needless to say, once you start losing, you keep on losing.</p> + <p>The owner of the restaurant was managing everything with his family, he cooked, his wife was at the till, his daughters serving. He told me he needed me in June and August, but not in July because his nephew was coming, so I decided to use that month and the money I had earned to visit Italy. Just like that.</p> + <h3>Io sono inglese.</h3> + <p>I took the pedestrian ferry from Bastia to Livorno which was more like a yacht. Everyone was lying in the sun on the deck, the sea was calm and flat. I visited Pisa, but the tourist merchants ruined it for me. Unfortunately, the hitchhiking from there to Florence was a pain, it took me 3 days.</p> + <p>Today I don’t recall the exact route I took, just that I zigzagged across Italy without any plan, sleeping most of the time outdoors under the stars. There are only images left, such as a bend in a wide, beautiful road lined with houses and cypresses in the dusty heat near Livorno. I know I was in Bari and Siena, but I don’t remember them, except perhaps the bridges in Siena.</p> + <p>In Florence I saw beautiful buildings, statues, and artworks, oddly I remember seeing a few birds being scared off by a bus at the end of an alley in shadows. The city was so rich in art and history that I almost choked on it, but it was also where I discovered cappuccino.</p> + <p>A young couple from Turin gave me a lift in their car, stopping at a house on the way. I was intrigued by the coolness of the marble floor in the house, having been raised on English carpets. The difference in climate was evident. We ended up on a populous beach near Rimini on the Adriatic where I spent the afternoon with them eating ice cream. I remember seeing an oil rig in the distance and red sand. Although I didn’t, and still don’t, speak Italian, sign language worked well.</p> + <p>At one point I was so tired that I fell asleep on the side of a road to Venice that ran through a marsh with double-trailer trucks passing too close, but I had to stop. Venice, where I had to pay extra to heat up a slice of pizza. I soon found that you don’t have to follow the big tourist arrows but walk behind them to see the real Venice with its flapping clotheslines. At the station, I sat on its large steps before taking a train with wooden slatted seats to Bologna, a name I knew only for its relation to spaghetti.</p> + <p>A small man with dark glasses and his wife welcomed me and gave me food and a bed for the night. They showed me a map of a trip they had just taken in their Citroen DS around the Mediterranean, from Bari through Greece, Egypt, North Africa, Morocco, Spain, France and back to Italy. He told me he was ill, pointing to his head, and explained that wanted to do it before he died.</p> + <p>A man and his son picked me up in an old van. They asked me if I wanted to come to the watermelon festival in his village. I ended up spending the night with about 30 people of all ages in a barn with straw, an old record player, watermelons and wine. I drank, danced, played with the kids, and laughed with everyone. The next morning he took me back to the main road so I could hitch a ride. He bid me farewell and, in doing so, slipped a few banknotes into my pocket. As they drove away I was filled with emotion by these people that had little, but would give everything they had to a stranger.</p> + <p>I was picked up by a jovial truck driver in the Apennines, who stopped to buy us fire-baked, slightly charred potatoes in a paper bag. I arrived in a mountain village where the whole population seemed to be out in the square around 7pm talking, playing music, drinking and laughing. Another day on foot with my rucksack digging into my shoulders, I turned a corner on the road through these mountains and saw clouds below me in a valley.</p> + <p>I saw some yellow volcanic fumaroles probably near Calabria, but I made my way back to Avellino, where I was hosted by some happy joint smoking students. One of them lived alone with his mother who put me up for a couple of nights. A major earthquake occurred there five years later. I’ve always wondered what happened to them, and regret not remembering any names.</p> + <p>Near Naples, a priest picked me up in his little Fiat 500. I explained that I was on my way to visit Pompeii. He took me there and spent a couple of hours showing me some of the more hidden wall paintings.</p> + <p>Years later, when I saw the film “Le Corniaud” with Bourvil and Louis de Funes, I remembered hitching a ride on the same road Bourvil took around the Bay of Naples, the famous bay Caligula crossed on horseback by lining up boats.</p> + <p>I arrived in Genoa the night before the ferry left for Corsica. I remember crossing high motorway viaducts and a kind harbour keeper who offered me a coffee and showed me an alcove where I could sleep.</p> + <p>Back in Corsica, I worked hard in the restaurant, but also played hard, swimming in the sea with everyone every day for hours. One night we had a delicious steak. Everyone at the table was giggling at me, it turned out they wanted to see how an Englishman liked his horse meat.</p> + <p>I remember distant electric storms at night over the sea like a curtain of fireworks. A car ride in the back seat with the pizza man and waiter to the west coast, through mountains where the sun reflected off the pink granite slopes. A large fish that circled me in a flash while swimming. A tan like I’d never had before, and never since. The discreet delivery of local moonshine, where I found bottles in the early hours next to the restaurant door. The revelation of fromage blanc with sugar. The comical daily slow-motion delivery of bread from the baker’s van. An embarrassingly failed attempt to add a gas cylinder to a whipped cream dispenser. Endless dirty dishes and plates to wash.</p> + <p>But above all the youthful sun.</p> + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/static/woodworking.html b/static/woodworking.html new file mode 100755 index 0000000..7f3a72b --- /dev/null +++ b/static/woodworking.html @@ -0,0 +1,43 @@ +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > + <meta name="viewport" content="width=device-width, initial-scale=1.0" > + <link rel="stylesheet" href="/style.css" /> + <link rel="icon" type="image/x-icon" href="/favicon.ico"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/static/index.php">Back</a> + </header> + <main> + <article> +<h2>Woodworking</h2> +<h5> +2023-11-30 +</h5> +<p>When I retired I concluded that 35 years spent on other people's computers was enough, +and that I had to move on to something completely different, because life is too short. +</p> +<p>I've been interested in wood since I was a child, probably because of the smell +of it and the nostalgia of watching my father make things. +</p> +<ul> +<li class="articlelink"><a href="https://www.lairdubois.fr/philipw" target="_blank">My page at l'Air du Bois</a></li> +<li class="articlelink"><a href="/static/woodworking/Scie-%C3%A0-ruban-fait-maison.html">Home made bandsaw</a></li> +<li class="articlelink"><a href="/static/woodworking/oland-tools.html">Home made Oland lathe tools</a></li> +<li class="articlelink"><a href="/images/tournages/index.php">Turnings</a></li> +<li class="articlelink"><a href="/images/pyrogravure/index.php">Wood burning</a></li> +<li class="articlelink"><a href="/images/chantournage/index.php">Scroll sawings</a></li> +<li class="articlelink"><a href="/images/menuiserie/index.php">General woodwork</a></li> +</ul> + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/static/woodworking/Scie-à-ruban-fait-maison.html b/static/woodworking/Scie-à-ruban-fait-maison.html new file mode 100755 index 0000000..85e3bda --- /dev/null +++ b/static/woodworking/Scie-à-ruban-fait-maison.html @@ -0,0 +1,52 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/static/woodworking.html">Back</a> + </header> + <main> + <article> +<h2>Menuisierie: Scie à ruban fait maison</h2> +<h5>2022-03-01</h5> +<p><img style="width:200px;" src="/images/scie-completed-4.jpg"></p> +<p>Cette scie à ruban de 40 cm (16”) m’a coûté moins de 400€ à fabriquer, contre au moins 1 800€ pour une scie commercial. +Bien sûr, cela dépend de ce que vous avez sous la main ou pouvez dénicher, et du temps dont vous disposez.</p> + +<p>Je n’ai pas voulu faire une ouvre d’art, mais un outil fonctionnel pour débiter des ébauches de bols pour tournage, et d'autres travaux courants.</p> + +<pre>Moteur: 2CV 220v 1400 tr/m +Hauteur de coupe: 27cm +Longueur des lames: 2690mm +Largeur des lames: 6 – 18mm</pre> + +<h3>Construction</h3> +<ul> +<li class="articlelink"><a href="/articles/2021/Scie-à-ruban-fait-maison-(1).html">Scie à ruban fait maison (1)</a></li> +<li class="articlelink"><a href="/articles/2021/Scie-à-ruban-fait-maison-(2).html">Scie à ruban fait maison (2)</a></li> +<li class="articlelink"><a href="/articles/2021/Scie-à-ruban-fait-maison-(3).html">Scie à ruban fait maison (3)</a></li> +<li class="articlelink"><a href="/articles/2021/Scie-à-ruban-fait-maison-(4).html">Scie à ruban fait maison (4)</a></li> +<li class="articlelink"><a href="/articles/2021/Scie-à-ruban-fait-maison-(5).html">Scie à ruban fait maison (5)</a></li> +<li class="articlelink"><a href="/articles/2021/Scie-à-ruban-fait-maison-(6).html">Scie à ruban fait maison (6)</a></li> +<li class="articlelink"><a href="/articles/2022/Scie-à-ruban-fait-maison-(7).html">Scie à ruban fait maison (7)</a></li> +<li class="articlelink"><a href="/articles/2022/Scie-à-ruban-fait-maison-(8).html">Scie à ruban fait maison (8)</a></li> +<li class="articlelink"><a href="/articles/2022/Scie-à-ruban-fait-maison-(9).html">Scie à ruban fait maison (9)</a></li> +<li class="articlelink"><a href="/articles/2022/Scie-à-ruban-fait-maison-(10).html">Scie à ruban fait maison (10)</a></li> +<li class="articlelink"><a href="/articles/2022/Scie-à-ruban-fait-maison-(11).html">Scie à ruban fait maison (11)</a></li> +<li class="articlelink"><a href="/articles/2022/Scie-à-ruban-fait-maison-(12).html">Scie à ruban fait maison (12)</a></li> +</ul> + + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/static/woodworking/oland-tools.html b/static/woodworking/oland-tools.html new file mode 100755 index 0000000..3962fa7 --- /dev/null +++ b/static/woodworking/oland-tools.html @@ -0,0 +1,56 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/static/woodworking.html">Back</a> + </header> + <main> + <article> +<h2>Menuiserie: Oland Wood Lathe Tools</h2> +<h5> +2023-11-29 +</h5> +<img src="/images/oland-tools.jpg" alt="Mes outils Oland"> + +<p>Dans les années 70, Knud Oland a inventé des outils de tournage économiques et faciles à fabriquer par soi-même.</p> + +<p>Parmi les explications qu’on trouve sur l’internet, il y a surtout ceux-ci que j’ai noté:</p> + +<ul> +<li><a href="http://www.aroundthewoods.com/oland.shtml">aroundthewoods.com</a></li> +<li><a href="https://youtu.be/cXMFmPA6GWQ">youtu.be/cXMFmPA6GWQ</a></li> +<li><a href="https://youtu.be/QZzIoZtqI14">youtu.be/QZzIoZtqI14</a></li> +</ul> + +<p>On peut former toutes les profiles qu’on désire, et les utiliser en ciseau ou racloir.</p> + +<p>Pour les faire moi-même, avec les matériaux disponibles en France, +j’ai utilisé des tuyaux en acier galvanisés 15/21 (<a href="https://www.toolstation.fr/tube-galvanise/p40200">Toolstation</a>), +des barreaux acier HSS 12 × 12mm (<a href="https://mgdiffusion.net/outils-de-tour/9177-barreau-a-outils-hss-section-carree-3x3-a-20x20-mm.html#/763-section-12_x_12_mm">exemple</a>), des vis sans tête de 6mm x 6mm, et une taraud M6.</p> + +<p>Il suffit de percer deux trous dans le tuyau et de créer des pas de vis avec un taraud.</p> + +<p>On peut utiliser des tailles différentes, mais la difficulté est de trouver des tuyaux qui correspond aux barreaux. +On peut aussi percer des ronds d’acier pour loger le barreau mais c’est plus compliqué.</p> + +<p>Noter que les barreaux sont en HSS M2, les aciers cobalt apportent de la dureté à haute température, +mais c'est n’est pas utile lorsque on travaille le bois.</p> + +<p>Concernant l’affûtage, d’après Knud Oland, "Il n'y a pas de bonne ou mauvaise façon d'affûter, l'objectif est qu'il coupe pour vous"</p> + +<p>#woodturning #tools #homemade</p> + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/static/woodworking/tour-à-bois.html b/static/woodworking/tour-à-bois.html new file mode 100755 index 0000000..80a78dd --- /dev/null +++ b/static/woodworking/tour-à-bois.html @@ -0,0 +1,43 @@ +<!doctype html> +<html lang="EN"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="/style.css"> + <title>Bloggings</title> + </head> + <body> + <header> + <h1>Bloggings</h1> + <a href="/static/woodworking.html">Back</a> + </header> + <main> + <article> +<h2>Home made wood lathe</h2> +<h5> +2023-11-30 +</h5> +<p>Not having the budget of a prince, I made my own wood lathe.</p> +<p>It can turn bowls up to 38cm in diameter, has a 1400W 1400 rpm motor, and so far 3 speeds, 300, 600 and 1400 rpm.</p> +<p>Today I'm having a lot of fun with it, and this kind of article is always a target for endless improvements.</p> + +<ul> +<li class="articlelink"><a href="/images/tour/index.php">Building the lathe</a></li> +<li class="articlelink"><a href="/images/tournages/index.php">Some of my turnings</a></li> +</ul> + +<iframe id="lbry-iframe" width="560" height="315" src="https://odysee.com/$/embed/DIY-Wood-lathe/749d3e009f0600b332d5641f41aadcef42ca03e3?r=F2Jwby9H75djSbAXdTgLvVdZ7L2efVDc" allowfullscreen></iframe> + +<p>Video of the lath working with a rough cherry bowl ready for final shooting.</p> + +<h3>Update:</h3> +<p>The bearing support wore out and rather than build a new one I replaced it with a <a href="http://wittamore.fr/articles/2024/A-new-lathe.html">Coronet Herald</a></p> + + <p> </p> + </article> + </main> + <footer> + <p>§</p> + </footer> + </body> +</html> diff --git a/style.css b/style.css new file mode 100755 index 0000000..fa678bf --- /dev/null +++ b/style.css @@ -0,0 +1,172 @@ +body, +html { + height: 100%; + color: #111; + margin: 10px; + font-family: "monospace"; + font-size: 1em; + line-height: 1.5em; + background-color: #fff; +} +html { + margin: auto; + width: 100%; + max-width: 80ch; +} +header { + margin-bottom: 1em; +} +.banner { + display: inline-block; + width: 100%; +} +.banner img { + float: left; + width: 4.5em; + height: 4.5em; +} +.banner p { + position: relative; + left: 0.4em; +} + +footer { + margin-top: 1em; + border-top: 1px solid #111; +} +p, +table, +thead, +tbody, +pre, +code, +div { + width: 100%; +} +p { + text-align: justify; +} +tbody tr { + background-color: #eee; +} +ul { + margin: 0; + padding: 0; +} +li { + list-style-type: disc; +} +li.articlelink { + list-style: none; + margin: 0; + padding: 0; +} +h1 { + color: #222; + font-size: 3em; +} +h2, +h3, +h4, +h5, +h6 { + color: #222; +} +h2.blogyear { + background-color: gold; + padding: 4px; +} +a { + color: #00e; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +strong, +em { + color: red; + padding-left: 2px; + padding-right: 2px; +} +b { + font-weight: bold; +} +small { + color: #8ec07c; +} +img { + max-width: 100%; +} +blockquote { + border-left: 2px solid khaki; + padding-left: 20px; + margin-left: 0px; +} +blockquote p { + font-style: italic; +} +hr { + height: 0; + border: 0; + border-top: 2px solid #458588; + margin: 1em 0; + padding: 0; +} +td { + padding-left: 1ch; + padding-right: 1ch; +} +th { + color: #222; + background-color: gold; + padding: 4px; +} +pre { + font-family: monospace; + white-space: pre-wrap; + font-size: 0.8em; + border-left: 1px solid #999999; + padding: 10px; + text-align: left; +} +code { + font-family: monospace; + background: #eee; + white-space: pre-wrap; + display: block; + padding: 10px; + text-align: left; +} + +.wrapper { + position: relative; + padding-bottom: 56.25%; /* 16:9 */ + padding-top: 25px; + height: 0; +} +.wrapper iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +figure { + margin: 0; + margin-top: 30px; + background: #8ec07c; + width: 400px; + max-width: 100%; +} +figcaption { + margin-bottom: 10px; + padding: 10px; + background: #8ec07c; + color: #111; + font-size: 1em; +} +.center { + text-align: center; +} diff --git a/txt/account_philip.txt b/txt/account_philip.txt new file mode 100755 index 0000000..d87ae0c --- /dev/null +++ b/txt/account_philip.txt @@ -0,0 +1,51 @@ +# @File Name : philip.rc + +# base +set realname = "Philip Wittamore" +set from = "philip@wittamore.fr" +set use_from = "yes" +set envelope_from = "yes" + +# +set sendmail = "/usr/bin/msmtp -a philip" +set mbox_type = Maildir + +# folders +set folder=~/.mail/philip/ +set spoolfile = "+Inbox" +set trash = "+Trash" +set postponed = "+Drafts" +set record = "+Sent" + +# dsn +set dsn_notify = "failure,delay" +set dsn_return = hdrs + +# sidebar +unmailboxes * +mailboxes `~/Apps/Neomutt/bin/get-mailboxes.sh ~/.mail/philip` + +# GPG +#set crypt_use_gpgme=yes +#set crypt_reply_sign=yes +#set crypt_reply_encrypt=yes +#set crypt_reply_sign_encrypted=yes +#set crypt_autosign=yes +#set crypt_verify_sig=yes + +#set pgp_default_key="9FD4104BF8734069C65C9E7062C85864C0420C9A" +#set pgp_autosign=yes +#set pgp_replyencrypt=yes +#set pgp_replysign=yes +#set pgp_timeout=1800 + +# signature +set sig_on_top = yes +set sig_dashes = yes +set signature = "iconv -f utf-8 ~/Apps/Neomutt/accounts/philip.sig.rc|" + +# +subscribe neomutt-users + +# macros +macro index <F6> "<shell-escape>mbsync philip<enter>" diff --git a/txt/display_filter.txt b/txt/display_filter.txt new file mode 100755 index 0000000..e45bf02 --- /dev/null +++ b/txt/display_filter.txt @@ -0,0 +1,53 @@ +#!/bin/bash +# lun. 29 mars 2021 09:05:05 CEST +# Neomutt display_filter.sh +# filter for pager display only + +sed -e ' + +# clean up neomutt notifications +/\[-- Visualisation.* --\]/d +/\[-- Type: text.* --\]/d +/\[-- Type.* --\]/d +/\[-- Pièce jointe #[0-9] --\]/d +s/Pièce jointe #[0-9]: //g +/\[-- .* pas disponible .* --\]/d + +# url tidy +# put each url on a new line +s/http/\nhttp/g +# replace last /blahblah with /... +#s|\(http.*\)/.*|\1\/...| + +# hide messy inline pdf +/^%PDF-1.4/,/^%%EOF/d + +# add newline after --] +s/--\]/--\]\n/g + +# CID +s/\[cid/\n\[cid/g + +# delete [img] tags +s/\[img\]//g + +# change ugly lines +s/━/-/g + +# remove last < +s/<*$// + +# remove lone [ +s/^\[$// + +# delete single spaces at the beginning of lines +s/^ //g + +# delete multiple spaces +s/[[:space:]]\+/ /g + +# delete multiple blank lines + +' | awk '!NF {if (++n <= 1) print; next}; {n=0;print}' + + diff --git a/txt/dwm-config.txt b/txt/dwm-config.txt new file mode 100755 index 0000000..6d4e678 --- /dev/null +++ b/txt/dwm-config.txt @@ -0,0 +1,139 @@ +/* See LICENSE file for copyright and license details. */ +#include <X11/XF86keysym.h> + +/* appearance */ +static const unsigned int borderpx = 1; /* border pixel of windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = { "JetBrainsMono Nerd Font Mono:size=11" }; +static const char dmenufont[] = "JetBrainsMono Nerd Font Mono"; +static const char col_gray1[] = "#222222"; +static const char col_gray2[] = "#444444"; +static const char col_gray3[] = "#bbbbbb"; +static const char col_gray4[] = "#eeeeee"; +static const char col_rust[] = "#C74C00"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_rust }, + [SchemeSel] = { col_gray4, col_rust, col_rust }, +}; + +/* tagging */ +static const char *tags[] = { "1", "2", "3", "4"}; //, "5", "6", "7", "8", "9" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "ghostty", NULL, NULL, 1 << 0, 0, -1 }, + { "librewolf", NULL, NULL, 1 << 1, 0, -1 }, + { "Chromium", NULL, NULL, 1 << 1, 0, -1 }, + { "Gimp", NULL, NULL, 1 << 2, 1, -1 }, + { "gThumb", NULL, NULL, 1 << 2, 1, -1 }, + { "galculator", NULL, NULL, 1 << 8, 1, -1 }, + { "Orage" , NULL, NULL, 1 << 8, 1, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_rust, "-sf", col_gray4, NULL }; +static const char *termcmd[] = { "ghostty", NULL }; +static const char *wwwcmd[] = { "librewolf", NULL }; +static const char *brighter[] = { "brightnessctl", "set", "10%+", NULL }; +static const char *dimmer[] = { "brightnessctl", "set", "10%-", NULL }; +static const char *bluetooth[] = { "blueman-manager", NULL }; +static const char *scrotcmd[] = { "screenshot.sh", NULL}; + +static const Key keys[] = { + /* modifier key function argument */ + { 0, XF86XK_AudioMute, spawn, SHCMD("~/.local/bin/dwm-audio voltoggle") }, + { 0, XF86XK_AudioLowerVolume, spawn, SHCMD("~/.local/bin/dwm-audio volquieter") }, + { 0, XF86XK_AudioRaiseVolume, spawn, SHCMD("~/.local/bin/dwm-audio vollouder") }, + { 0, XF86XK_AudioMicMute, spawn, SHCMD("~/.local/bin/dwm-audio mictoggle") }, + { 0, XF86XK_MonBrightnessDown, spawn, {.v = dimmer } }, + { 0, XF86XK_MonBrightnessUp, spawn, {.v = brighter } }, + { 0, XK_Print, spawn, {.v = scrotcmd } }, + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, XK_w, spawn, {.v = wwwcmd } }, + { MODKEY, XK_r, spawn, SHCMD("rofi -show run") }, + { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_Right, focusstack, {.i = +1 } }, + { MODKEY, XK_Left, focusstack, {.i = -1 } }, + { MODKEY, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY, XK_agrave, view, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY|ShiftMask, XK_agrave, tag, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_semicolon, tagmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_c, killclient, {0} }, + { MODKEY|ShiftMask, XK_q, quit, {0} }, + { MODKEY|ShiftMask, XK_x, spawn, SHCMD("rofi -show power-menu -modi power-menu:rofi-power-menu") }, + { MODKEY|ShiftMask, XK_b, spawn, {.v = bluetooth } }, + TAGKEYS( XK_ampersand, 0) + TAGKEYS( XK_eacute, 1) + TAGKEYS( XK_quotedbl, 2) + TAGKEYS( XK_apostrophe, 3) + TAGKEYS( XK_parenleft, 4) + TAGKEYS( XK_minus, 5) + TAGKEYS( XK_egrave, 6) + TAGKEYS( XK_underscore, 7) + TAGKEYS( XK_ccedilla, 8) + TAGKEYS( XK_agrave, 9) +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/txt/get-mailboxes.txt b/txt/get-mailboxes.txt new file mode 100755 index 0000000..b6ceff0 --- /dev/null +++ b/txt/get-mailboxes.txt @@ -0,0 +1,17 @@ +#!/bin/sh + +maildir=$1 + +# Find these specific paths so they stick at the top of the list +MAILBOXES='"+Inbox" "+Sent" "+Trash" "+Drafts" "+Junk" ' + +#Find all other paths, excluding those from above +MAILBOXES=$MAILBOXES`find "${maildir}" -type d -name cur \ + -a -not \( -ipath "*Inbox*" \ + -o -ipath "*Sent*" \ + -o -ipath "*Drafts*" \ + -o -ipath "*Trash*" \ + -o -ipath "*Junk*" \ + \) | sed -e 's/\/cur$/\"/g' -e "s#^${maildir}/# \"+#" | sort` + +echo $MAILBOXES
\ No newline at end of file diff --git a/txt/key.txt b/txt/key.txt new file mode 100755 index 0000000..5e4e6fe --- /dev/null +++ b/txt/key.txt @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQGNBGBRup8BDADeC7QcOfwQKaCSc06dgV/6t6feuvfp5JCf5oDgwLddZ3/2dE8M +Rr+HbFNGhkbijWeFGuoFIJG8jczsyjqTYYYGHYw5/q7zHtPc1p1krpeOSycJvND6 +qGSojGJl3a5yfW/Lf2NitTn5lEoSZ0qod8Amy0Up4J24tWZEs1A2IJwlgufmakJP +ibkg/Akpw3BrAbjedQBoG47CPeTMVBxW1C5syDLSA5/g+32ZO3uKqZ2xIcR61NuJ +LJs459O/Lh1eUOsz6mfGKuFbzFp5wI83MmqLhwIeSwewk99AgPzWWPts3h1MUIb8 +JrOO8pugZslYiJT5kEum37+ufhfK78vqf/yRQt+NzDAlHgHSYPpNFexMURlw/gZT +Uw8oy4WjBtgisjps/ezDen4cLuuYbbrWU0giJDHPEh7r3Agg1KKxeRhtzkrT1TZH +SH3wfxbZcsavbaElhSO3RJ1rVWxMKyRyaSGmgaN8a8c+ZvJs79gO0dgCnS+YrPF8 +XX9AH3JkZZ0opuMAEQEAAbQnUGhpbGlwIFdpdHRhbW9yZSA8cGhpbGlwQHdpdHRh +bW9yZS5jb20+iQHUBBMBCAA+FiEEn9QQS/hzQGnGXJ5wYshYZMBCDJoFAmBRup8C +GwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQYshYZMBCDJq8pQwA +ko5TJ1RR/H1SRWil4MhtvLB+2U9iIZkDu/mnBqHSQRfeu/9BQVdMdTR8tJ9mLpDy +u2ZtlrcYF5zuYR3vW6QtFogJYpv1M6SBjCtXswB/hEYVX7IhW2pgCrzzHI9cv6IY +rKxfzEh0Fcnp6WgC1Ed8DpDMRFU7+nWswPNRZXFV69HOf2BO9fDxWcUiBuz+qVqw +9y/YJWo4qdxYjsXtv00IvWgMw7LQ9P0P5qIXJXqikl6oefTatc4KMA4m1Bc/6TFl +QzWCstEW5gzGXJy0fNT7UhiYiA7uNQ27MrmB+kS5gK3pEFVNcH9yw2NTGBrFkbdB +fjWgBzexioQQTMO/Pcv0vjvm7WgZeUT2DZQp+IixlTB0dzgqJKzR1cHHKATVeXUu +N/AfMbIJA2zQw4FxO8hlvpeLST6yxhbSaUVOVPlODujfgfK28TFF2o/GLepHljGv +ck1UEO+Dup/XcJKmVJ+BsmJwNi3Qw2tKJ87njahLG2kJd95ibzDkDh2qEDagMmiv +uQGNBGBRup8BDACwxZO2fwJI/XO6noRNtc7BFqzO704xuPWHfO34wXoFmfSgOf8i +eUv/4yjK6Fyj9OB7O/OZtZ+gU0HqG1f5g1ofW+p764xC3Tlpt+OqGrjJK54vwj9N +ZcqJtqP0ujwJdUpTD07n5FzW3wqfZk2WevFr9pO4/f5F+YwxR03yFwzUk5CxYscy +iMg9uSL0m6zNufcbK2whWThGyuDq7oadqWgoilUumshJ3zCpoo/kEN0FWr43mkb9 +WgyfBoWP9wzbhu9qjyXLDhMq8t45kfUGZYrWuz1oMo7uFYkNHOrdqAQpCPqS9ZIi +caMINFVFJWObugevhK4nxtRt4oHL369QqC1RziRmBkcs/IZLFVT/bHudVn+omLOR +PCre4p15SVObBt7P1oi0YscsiGp1gbQiSKtEMmXXlzglPU/MyrA7ukO9sKMZ5wqz +R2V1QKbYuLx9UElvkNB9BUeK6En7YciPdjKKIe5LOa/1Dagdfa+k8FMZbBBtEDg1 +Dhn2qIgYS2NH0JkAEQEAAYkBvAQYAQgAJhYhBJ/UEEv4c0BpxlyecGLIWGTAQgya +BQJgUbqfAhsMBQkDwmcAAAoJEGLIWGTAQgyayIIL/2cJ41LazYSMXNNgTAD8Ncse +X2jBrT49x/EqwSAVhSb9JD5+7W7Oz5ofCRBQhrwYMGrrpxlo2HEM8wDRBD26pTiO +nxdwzLRIEvD3V+hUMMgc58/saEHF8Dcy6MokeO8CmXpLktg9d04aBhSNfIhFOP6R +pqu3HAWeJXpkMV0T4OTgZQS0bz2nYvPdiZb/o7nCDklEWIFs4Q/QnJ2FtW1Qk091 +Bgowh8k3u/7pXeqW5deP3lZXssVnzDY4/g26edogoT71r/qBMLGLfY11Hrse5lwR +9U/gr7ju3azx1Mo8aLlbwJyzZYwoH+5fdk4W6XC+SfT+A9sLYMaAMe7CBnhWHhor +1uC94Wp6gIqt3At3DpQWn0oyYu4x+tVUJ+223rgdAQvSi6hzJ3ffqG85pT85RlHD +Yauxsn0xMZwThgmqphS28dpcrXuHputnUQZKUpJiJVeJsuMSSjaRB3bYYasxLjwK +h+0NuvYumBMTfvXfQiOMY1g3Y5xl5yAABsCUaKQdMA== +=cE2x +-----END PGP PUBLIC KEY BLOCK----- diff --git a/txt/mailcap.txt b/txt/mailcap.txt new file mode 100755 index 0000000..189e257 --- /dev/null +++ b/txt/mailcap.txt @@ -0,0 +1,11 @@ +# +text/html; librewolf %s; +text/html; cha -T text/html %s;copiousoutput; +# +audio/*; cp %s ~/Apps/Neomutt/tmp \; vlc ~/Apps/Neomutt/tmp/$(basename %s) > /dev/null 2>&1 & +image/*; cp %s ~/Apps/Neomutt/tmp \; ristretto ~/Apps/Neomutt/tmp/$(basename %s) > /dev/null 2>&1 & +# +application/ics; /usr/bin/calcurse -i %s && notify-send "Entry added to calendar 📅" +text/calendar; /usr/bin/calcurse -i %s && notify-send "Entry added to calendar 📅" +text/x-vcard; ~/Apps/Neomutt/bin/mutt.vcard.filter; copiousoutput; +application/*; cp %s ~/Apps/Neomutt/tmp \; xdg-open ~/Apps/Neomutt/tmp/$(basename %s) > /dev/null 2>&1 & diff --git a/txt/mutt-trim.txt b/txt/mutt-trim.txt new file mode 100755 index 0000000..2378ea6 --- /dev/null +++ b/txt/mutt-trim.txt @@ -0,0 +1,171 @@ +#!/usr/bin/perl +# +# "Beautify" quoted message and make it "ready-to-reply". Originally by Michael Velten. + +use utf8; + +# keep quotes nested up to 3rd level +my $ind_max = 3; +# # put 1 empty line between first and following quote +# my $gap = 1; + +my $name = '[[:alpha:]]+([\'`-][[:alpha:]]+|[.])*'; +my $fullname = '\b(' . $name . '[,]?\s+)*' . $name . '\b'; + +# Possible reply greetings (regexes) (note that '> ' will be prefixed) +my @greetings = ( + 'Dear\s+' . $fullname . '([,.]|\s*!)?', + '[Hh](ello|i|ey)' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + 'Sehr geehrter?\s+' . $fullname . '([,.]|\s*!)?', + 'Lieber?\s+' . $fullname . '([,.]|\s*!)?', + 'Guten Tag' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + '[Hh]allo' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + '[Mm]oin' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + '[Mm]esdames(,| et) [Mm]essieurs([,.]|\s*!)?', + 'M(adame)\s+' . $fullname . '([,.]|\s*!)?', + 'M(onsieur)\s+' . $fullname . '([,.]|\s*!)?', + '[Cc]her\s+' . $fullname . '([,.]|\s*!)?', + '[Cc]hère\s+' . $fullname . '([,.]|\s*!)?', + '[Bb]onjour' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + '[Ss]alut' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + 'Senhor(ita|a)?' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + 'Sra?\.?' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + 'Car(íssim)?[ao]s?' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + 'Prezad(íssim)?[ao]s?' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + 'Estimad(íssim)?[ao]s?' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + '[Bb]om [Dd]ia' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + '[Bb]oa ([Tt]arde|[Nn]oite)' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + '[Oo](i|lá|la)' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + '[Aa]l[ôo]' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + '[Hh]ola' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + 'Se[ñ]or(ita|a)?' . '(\s+' . $fullname . ')?' . '([,.]|\s*!)?', + ); + +# Possible reply "greetouts" (regexes) (note that '> ' will be prefixed) +my @greetouts = ( + '([Ww]ith )?(([Kk]ind|[Bb]est|[Ww]arm) )?([Rr]egards|[Ww]ishes)([,.]|\s*!)?', + '[Bb]est([,.]|\s*!)?', + '[Cc]heers([,.]|\s*!)?', + '[Mm]it ([Vv]iel|[Bb]est|[Ll]ieb|[Ff]reundlich)en [Gg]r([ü]|ue)([ß]|ss)en([,.]|\s*!)?', + '(([Vv]iel|[Bb]est|[Ll]ieb|[Ff]reundlich)e )?[Gg]r([ü]|ue)([ß]|ss)e([,.]|\s*!)?', + '(([[Bb]est|[Ll]ieb|[Ff]reundlich)e[rn] )?[Gg]ru([ß]|ss)([,.]|\s*!)?', + '[Mm]it (([[Bb]est|[Ll]ieb|[Ff]reundlich)em )?[Gg]ru([ß]|ss)([,.]|\s*!)?', + '([LV]|MF)G([,.]|\s*!)?', + '(([Tt]rès|[Bb]ien) )?([Cc]ordi|[Aa]mic)alement([,.]|\s*!)?', + '[Aa]miti[é]s?([,.]|\s*!)?', + '[Aa]tenciosamente([,.]|\s*!)?', + '[Aa]tt([,.]|\s*!)?', + '[Aa]abraços?([,.]|\s*!)?', + '[Aa]tentamente([,.]|\s*!)?', + '[Cc]ordialmente([,.]|\s*!)?', + ); + +my $word = '[[:alpha:]]+([\'`-][[:alpha:]]+)*'; + +# my $saw_greeting = 0; +# my $saw_leadin = 0; +# my $saw_greetout = 0; +my $saw_own_sig = 0; +my $saw_blank_line = 0; +my $inds_other_sig = 0; +my $quote_header = 0; +my $extra_pref = ''; +# my $prev_inds = 0; + +my (@mail, @purged_mail); + +my $msg = shift; +die "Usage: $0 MAIL" unless $msg; +open(MAIL, "+<:encoding(UTF-8)", $msg) or die "$0: Can't open $msg: $!"; +push(@mail, $_) while <MAIL>; # Read whole mail + +# Process whole mail +LINE: +foreach my $line (@mail) { + +# Treat non-quoted lines as is + if ($line !~ /^>/) { + push(@purged_mail, $line); + next LINE; + } + +# Keep all lines after my own signature unmodified + if ($line =~ /^--\s?$/ || $saw_own_sig) { + $saw_own_sig = 1; + push(@purged_mail, $line); + next LINE; + } + + # $line =~ tr/\xA0/ /; +# tighten "> > " to ">> " + my ($pref, $suff) = $line =~ /^([>[:space:]]+)(.*)$/; + $pref =~ s/(>\s*(?!$))/>/g; +# reduce multiple pre- and post-blanks to one post-blank + $pref =~ s/^\s*(>+)\s*/$1 /; + $line = $pref . $suff . "\n"; + +# prepend additional '>' for each Outlook quote header + if ($line =~ /^>+ [-_=]{3,}\s*$word(\s+$word)*\s*[-_=]{3,}$/) { + $quote_header = 1; + next LINE; + } +# first line after Outlook quote header that does not start with ...: + if ($quote_header == 1 && $line !~ /^>+ ([-*]\s*)?$word(\s+$word)*\s*:\s+/) { + $extra_pref = '>' . $extra_pref; + $quote_header = 0; + } + $pref = $extra_pref . $pref; + $line = $pref . $suff . "\n"; + +# skip line if number of '>'s is greater than $ind_max + my $inds = $pref =~ tr/>//; + next LINE if $inds > $ind_max; + +# Remove other signatures + if ($line =~ /^>+ --\s?$/) { + $inds_other_sig = $inds; + } + if ($inds == $inds_other_sig) { + next LINE; + } else { + $inds_other_sig = 0; + } + +# Remove quoted greeting + # unless ($saw_greeting) { + foreach my $greeting (@greetings) { + if ($line =~ /^>+ $greeting$/) { + # $saw_greeting = 1; + next LINE; + } + } + # } + +# Remove quoted "greetout" + # unless ($saw_greetout) { + foreach my $greetout (@greetouts) { + if ($line =~ /^>+ $greetout$/) { + # $saw_greetout = 1; + next LINE; + } + } + # } + +# Remove quoted filler lines + if ($line =~ /^>+ \s*(-*|_*|=*|\+*|#*|\**)$/) { + next LINE; + } + +# Insert $gap empty lines between different quote levels + # $line = "\n" x $gap . $line if $prev_inds < $inds; + # $prev_inds = $inds; + +# Save purged line + push(@purged_mail, $line); +} + +# Overwrite original mail with purged mail +truncate(MAIL, 0); +seek(MAIL, 0, 0); +print MAIL @purged_mail; +close(MAIL); diff --git a/txt/neomuttrc.txt b/txt/neomuttrc.txt new file mode 100755 index 0000000..423c555 --- /dev/null +++ b/txt/neomuttrc.txt @@ -0,0 +1,173 @@ +# .neomuttrc + +set config_charset = "utf-8" +set send_charset = "utf-8" +set charset = "utf-8" +set assumed_charset = "utf-8" +set attach_charset = "utf-8" +set rfc2047_parameters = yes + +set header_cache = ~/Apps/Neomutt/cache/headers +set certificate_file = ~/Apps/Neomutt/certificates +set message_cachedir = ~/Apps/Neomutt/cache/bodies +set alias_file = ~/Apps/Neomutt/aliases +set mailcap_path = ~/Apps/Neomutt/mailcap +set tmpdir = /tmp + +# mail settings +set mail_check = 30 +set timeout = 600 # time to wait until abort waiting for input. +set mail_check_stats # periodically calculate message statistics +set xterm_set_titles = yes +set sleep_time = 0 # Don't pause + +# mailcap +auto_view text/html text/plain + +# Sidebar +set sidebar_visible = yes +set sidebar_short_path # Shorten mailbox names +set sidebar_delim_chars = "/." # Delete everything up to the last / character +set sidebar_folder_indent = yes # Indent folders whose names we've shortened +set sidebar_indent_string = "▸" +set sidebar_format = "%B%?F? [%F]?%* %?N?%N/?%S" +set sidebar_width = 30 +set sidebar_sort_method = "unsorted" +set sidebar_divider_char ="│" + +# Folder hooks +folder-hook . `rm -f /Apps/Neomutt/tmp/*` # empty tmp folder +folder-hook "~/.mail/philip/*" "source ~/Apps/Neomutt/accounts/philip.rc" + +# Printing +set print = ask-no +set print_command = "~/Apps/Neomutt/bin/print_unicode.sh" + +# index display +set sort = threads +set sort_aux = last-date-received +set delete = yes # just delete don't ask + +# pager display +set pager = "builtin" +set smart_wrap = yes # lines wrapped at word boundary at screen edge. +set markers = no # don't add "+" at wrapped lines +set pager_stop = yes # don't continue to next message +set display_filter = "~/Apps/Neomutt/bin/display_filter.sh" + +# message display +ignore * +unhdr_order * +unignore Date: From: To: Cc: Bcc: X-Mailer: User-Agent: X-Spam-Flag: Subject: +hdr_order Date: From: To: Cc: Bcc: X-Mailer: User-Agent: X-Spam-Flag: Subject: +set quote_regex = "^([ \t]*[|>:}#])+" +set smileys = "(>From )|(:[-^]?[][)(><}{|/DP])" # catch some common false positives of $quote_regex +set pipe_decode = "yes" # weed headers and attempt to decode the messages first. + +# attachments +attachments +A */.* # Give all MIME parts an "attachment" disposition +set attach_save_dir = "~/Documents" +set attach_save_without_prompting +set abort_noattach = ask-yes +# French & English regex +set abort_noattach_regex = "\\<(attach|attached|attaché|attachés|attache|attachons|joint|jointe|joints|jointes|joins|joignons)\\>" + +# editor +set text_flowed = yes # don't cut lines +#set editor = "~/.neomutt/bin/mutt-trim %s; joe -nobackups %s" +set editor = "~/Apps/Neomutt/bin/mutt-trim %s; micro %s" + +# Replies +set attribution = "* %n <%a> [%(%y%m%d %H:%M)]:" +set fast_reply # do not ask for to, subject,... when replying. +set include = yes # include message + +# Forwards +set forward_format = "[Tr: %s]" +set forward_quote + +# Abook +set query_command = "abook --mutt-query '%s'" +macro index,pager a "<pipe-message>abook --add-email-quiet<return>" "Add this sender to Abook" +macro generic,index,pager \ca "<shell-escape>abook<return>" "launch abook" + +# Header Compression +set header_cache_compress_method = "zlib" +set header_cache_compress_level = 1 + +# PGP commands +set pgp_decode_command = "gpg2 %?p?--passphrase- 0? --no-verbose --batch --output - %f" +set pgp_verify_command = "gpg2 --no-verbose --batch --output - --verify %s %f" +set pgp_decrypt_command = "gpg2 --passphrase- 0 --no-verbose --batch --output - %f" +set pgp_sign_command = "gpg2 --no-verbose --batch --output - --passphrase- 0 --armor --detach-sign --textmode %?a?-u %a? %f" +set pgp_clearsign_command = "gpg2 --no-verbose --batch --output - --passphrase- 0 --armor --textmode --clearsign %?a?-u %a? %f" +set pgp_encrypt_only_command = "/usr/lib/neomutt/pgpewrap gpg2 --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust --encrypt-to $pgp_sign_as -- -r %r -- %f" +set pgp_encrypt_sign_command = "/usr/lib/neomutt/pgpewrap gpg2 --passphrase- 0 --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust --encrypt-to $pgp_sign_as -- -r %r -- %f" +set pgp_import_command = "gpg2 --no-verbose --import %f" +set pgp_export_command = "gpg2 --no-verbose --export --armor %r" +set pgp_verify_key_command = "gpg2 --no-verbose --batch --fingerprint --check-sigs %r" +set pgp_list_pubring_command = "gpg2 --no-verbose --batch --with-colons --list-keys %r" +set pgp_list_secring_command = "gpg2 --no-verbose --batch --with-colons --list-secret-keys %r" + + +# other settings + +#unset quit +# macro index,pager ",m" ":unset wait_key\n |/path/to/add_to_calcurse.sh<enter><exit>" +# set pager_index_lines = 6 # number of lines of a mini-index when in pager + +# Spam flag +# spam "X-Spam-Flag: YES" "" +# This pushes spam lines to right and it's ugly +# so instead I colour the line that contains X-Spam-Flag using +# color index color197 color233 ~hX-Spam-Flag +# in the colour theme + +# add my_hdr $mutt_version - $os_version +my_hdr User-Agent: `neomutt -v | head -1` - `uname -r` Linux + +# file browser +set sort_browser=reverse-date + +#### Key bindings +# sidebar +bind index,pager <C-up> sidebar-prev # Previous Mailbox +bind index,pager <C-down> sidebar-next # Next Mailbox +bind index,pager * sidebar-open # Open Highlighted Mailbox +# +bind pager <down> next-line # pager down one line +bind pager <up> previous-line # pager up one line +bind editor <Tab> complete-query # Abook completion + +#### Macros +# account swap +unset wait_key #don't wait for user input +macro index,pager <F2> "<sync-mailbox><enter-command>source ~/Apps/Neomutt/accounts/philip.rc<enter><change-folder>!<enter><redraw-screen>" +# toggle sidebar +macro index,pager <F5> "<enter-command>toggle sidebar_visible <enter><redraw-screen>" +# F6 in index runs mbsync, see philip.rc +macro pager <F6> "<pipe-message>~/Apps/Neomutt/bin/translate.sh<enter>" +# urlscan +macro index,pager \cb "<pipe-message>urlscan -c -d<Enter>" "call urlscan to extract URLs out of a message" +macro attach,compose \cb "<pipe-entry>urlscan -c -d<Enter>" "call urlscan to extract URLs out of a message" +# reload config +macro index,browser,pager \cr "<enter-command>source ~/Apps/Neomutt/.neomuttrc<enter>" +# View in graphic browser +macro attach 'V' "<pipe-entry>iconv -c --to-code=UTF8 > ~/Apps/Neomutt/tmp/mail.html<enter><shell-escape>floorp ~/.neomutt/tmp/mail.html<enter>" + +# SPAM +# Mark as not spam and move mail to inbox +macro index,pager n "<tag-prefix><pipe-entry>bogofilter -Mn<enter>\ +<tag-prefix><save-message>=Inbox<enter><enter><sync-mailbox>" "Mark as non spam & move to Inbox" +# Mark as not spam and do nothing else +macro index . "<tag-prefix><pipe-entry>bogofilter -Mn<enter>" "Mark as not spam in index" +macro pager . "<pipe-entry>bogofilter -Mn<enter>" "Mark as not spam in pager" +# Mark as spam and delete +macro index x "<tag-prefix><pipe-entry>bogofilter -MNs<enter><tag-prefix><save-message>=Junk<enter><enter><sync-mailbox>" +macro pager x "<pipe-entry>bogofilter -MNs<enter><save-message>=Junk<enter><enter><sync-mailbox>" + + +#### more config files +source ~/Apps/Neomutt/accounts/philip.rc # default account +source ~/Apps/Neomutt/themes/powerline.rc # powerline with nerd fonts +source ~/Apps/Neomutt/themes/venelles.rc # my color theme diff --git a/txt/poems.txt b/txt/poems.txt new file mode 100755 index 0000000..c6879e5 --- /dev/null +++ b/txt/poems.txt @@ -0,0 +1,70 @@ +--- +Joy of the grind +--- +Life is but a long farewell +Clawing at eternity's shell +A hopeless fight face entropy's might +This fleeting hare brings grave dispair +But pointless is our daily dread +Of closing circuit in our bed +So keep the pennies on the shelf +Existance is the prize itself + +--- +Emperors summer +--- +Of what ego did Augustus suffer +To name the month of dermic blusher +Did Julius reap the wheat himself +To name month fore weodmonath +Did butterflies of light divine +Flutter through their regal shine +Or was it summer's extasy +That lent this insight pleasantly +Make down the ages people utter +Their names while bathing golden butter + +--- +The universe observing +--- +The redshift of dim distant light +A foetus of the first bomb site +The universe's shock debut +Cast baryons, then me and you +Our evoluted thought process +Shaped science and our mind's egress +Man looked upon the pale redshift +And thus the cosmic mirror gift +Of universe's constant oeuvre +Enabling it to self observe + + +--- +A pointless myth +--- +I don't have the propensity +To appreciate god's density +To me the ecsis of humanity +Has no need of an almighty +Beliefs don't fly, they're all at sea +Comfort for some, a spirits decree +Cannot escape the reality +That life that's born of chemistry +Can assemble someone just like me + + +--- +Slices of a giant +--- +Mangled by biting teeth of iron +A giant falls impotent, still +The forest witnessed its lost scion +Dragged through ferns its lignin shell +Hefted, chained to diesel barrow +Sectioned and reduced by mill +No longer heard the song of sparrow +Shaped and nailed to form man's will + +--- + + diff --git a/txt/print_unicode.txt b/txt/print_unicode.txt new file mode 100755 index 0000000..24815a0 --- /dev/null +++ b/txt/print_unicode.txt @@ -0,0 +1,37 @@ +#!/bin/bash + +## print_unicode.sh +## By Amanda Hickman https://gist.github.com/amandabee/cf7faad0a6f2afc485ee +## Using https://github.com/baruchel/txt2pdf to print emails to PDF with unicode support. + +pdir="$HOME/.neomutt/tmp" +open_pdf=qpdfview +scriptloc="python3 $HOME/.neomutt/bin/text2pdf.py" + +# check to make sure that we're looking for txt2pdf in the right place +if ! command -v python3 $scriptloc >/dev/null; then + echo "Is $scriptloc installed?" + exit 1 +fi + +# create temp dir if it does not exist +if [ ! -d "$pdir" ]; then + mkdir -p "$pdir" 2>/dev/null + if [ $? -ne 0 ]; then + echo "Unable to make directory '$pdir'" 1>&2 + exit 2 + fi +fi + +# dump stdin to a tempfile +tmptxt="`mktemp $pdir/mutt_XXXXXXX.txt`" +cat >> $tmptxt + +tmpfile="`mktemp $pdir/mutt_XXXXXXXX.pdf`" + +# Actually write the text into a PDF. +$scriptloc --quiet -o $tmpfile $tmptxt +$open_pdf $tmpfile >/dev/null 2>&1 & +sleep 1 +rm $tmpfile +rm $tmptxt diff --git a/txt/translate.txt b/txt/translate.txt new file mode 100755 index 0000000..b19adcb --- /dev/null +++ b/txt/translate.txt @@ -0,0 +1,14 @@ +#!/bin/bash +# last-modified: 2021-03-20 20:38:27+01:00 +# requires https://github.com/soimort/translate-shell +# Arch Linux: yay -S translate-shell + +clear + +INPUT="$1" +cat $INPUT | sed -e '1,/^\r\{0,1\}$/d' | sed '/^>/d' > ~/Apps/Neomutt/tmp/tr.txt + +trans :en -no-browser file:///home/philip/Apps/Neomutt/tmp/tr.txt + +exit 0 + diff --git a/txt/update-fingerprint b/txt/update-fingerprint new file mode 100755 index 0000000..a151d2c --- /dev/null +++ b/txt/update-fingerprint @@ -0,0 +1,14 @@ +#!/bin/bash + +# update tls_fingerprint entry in ~/.msmtprc + +cd /home/philip + +# get the server fingerprint +KEY=$(msmtp --serverinfo --tls --tls-certcheck=off --host=mail.nagatha.fr --port=587 | grep SHA256:) +# remove spaces +KEY=$(echo $KEY | tr -d ' ') +# remove SHA256: +KEY=${KEY:7} +# replace tls_fingerprint line in .msmtprc +sed -i "s/^tls_fingerprint.*/tls_fingerprint $KEY/g" .msmtprc diff --git a/txt/venelles.txt b/txt/venelles.txt new file mode 100755 index 0000000..ea8eb12 --- /dev/null +++ b/txt/venelles.txt @@ -0,0 +1,77 @@ +# venelles.rc + +# general +color error color231 color212 +color indicator color0 color228 +color markers color210 color233 +color message color254 color233 +color normal color254 color233 +color prompt color254 color233 +color search color84 color233 +color status color249 color236 +color tilde color231 color233 +color tree color94 color233 +color quoted color71 color233 # all quoted lines +color signature color212 color233 +color progress white red + +# message index +color index color159 color233 ~A # all messages +color index color35 color233 ~P # from me +color index color46 color233 ~N # new message line +color index_flags color22 color233 ~Q # replied to line +color index color178 color233 ~F # flagged message line +color index_flags color196 color233 ~F # flag colour +color index color160 color233 ~D # deleted message line +color index color215 color233 ~T # tagged message line +color index color197 color233 ~hX-Spam-Flag #spam + +# sidebar +color sidebar_divider color192 color233 # The dividing line between the Sidebar and the Index/Pager panels +color sidebar_flagged color174 color233 # Mailboxes containing flagged mail +color sidebar_highlight color233 color101 # Cursor to select a mailbox +color sidebar_indicator color233 color228 # The mailbox open in the Index panel +color sidebar_new color46 color233 # Mailboxes containing new mail +color sidebar_ordinary color192 color233 # Mailboxes that have no new/flagged mails, etc. +color sidebar_spool_file color192 color233 # Mailbox that receives incoming mail +color sidebar_unread color192 color233 # Mailboxes containing unread mail + + +# message headers +color hdrdefault color233 color30 +color header color231 color30 ^Subject:.* +color header white red ^X-Spam-Flag:.* + +# message body +color attachment color222 color233 +color body color9 color233 [\-\.+_a-zA-Z0-9]+@[\-\.a-zA-Z0-9]+ # email addresses + +color body color231 color233 (^|[[:space:]])\\*[^[:space:]]+\\*([[:space:]]|$) # *bold* text +color body color231 color233 (^|[[:space:]])_[^[:space:]]+_([[:space:]]|$) # _underlined_ text +color body color231 color233 (^|[[:space:]])/[^[:space:]]+/([[:space:]]|$) # /italic/ text +color body color124 color233 Sent.from.* +color body color233 color155 "[;:]-*[)>(<|]" +color body color196 color233 "[*] .*:$" # Attribution (gukes) +color body color196 color233 "Le .* a écrit :$" # Attribution (French) +color body color196 color233 "On .* wrote:$" +color body color39 color233 "\\[[0-9]+\\]" +color body color143 color233 "^#v[-+]" +color body color39 color233 (https?|ftp)://[\-\.,/%~_:?&=\#a-zA-Z0-9]+ # URLs +color body color235 color233 "\\[.*\\]" +color body color39 color233 "\\[-- .* --\\]" + +# The older the email, the darker it appears. +color index color250 default "~d >7d" # older than 7 days +color index color245 default "~d >20d" # older than 20 days +color index color240 default "~d >30d" # older than 30 days +color index color238 default "~d >90d" # older than 90 days + +# PGP111 +color body color215 color233 "^Bonne signature de : .*" +color body color215 color233 "créée : .*" +color body color215 color233 "aka : .*" +color body color215 color233 "^ATTENTION ! Nous n'avons AUCUNE indication .*" +color body color215 color233 "^Empreinte : .*" +color body color196 color233 "^Signature problématique de : .*" + + |
