AutoBlueSky : Publication RSS vers Bluesky
Ce projet permet de publier automatiquement les nouveaux articles d’un flux RSS vers le réseau social Bluesky.
Le script est conçu pour Python 3. Il récupère les articles, extrait intelligemment l’image de couverture et le résumé (via les balises OpenGraph), et publie le tout sous forme de « Card » riche.
Prérequis
- Un serveur Linux (VPS, Raspberry Pi, etc.)
- Python 3 installé (
python3 --version) - Un compte Bluesky actif
Étape 1 : récupérer le mot de passe d’application
Pour utiliser ce script, vous ne devez jamais utiliser votre mot de passe de connexion habituel. Vous devez générer un mot de passe spécifique pour ce script.
- Connectez-vous à votre compte Bluesky.
- Rendez-vous dans les Paramètres > Mots de passe d’application (ou cliquez directement sur ce lien : https://bsky.app/settings/app-passwords).
- Cliquez sur le bouton « Ajouter un mot de passe d’application ».
- Donnez-lui un nom (ex:
Script RSS Blog). - Copiez la chaîne de caractères qui s’affiche (format :
xxxx-xxxx-xxxx-xxxx). C’est ce code que vous utiliserez dans la configuration à l’étape 3.
Étape 2 : installation
1. Préparation de l’environnement Python 3
Il est recommandé d’isoler les dépendances du script dans un environnement virtuel.
# Créez le dossier du projet
mkdir ~/autobluesky
cd ~/autobluesky
# Créez l'environnement virtuel Python 3
python3 -m venv venv
# Activez l'environnement
source venv/bin/activate
2. Installation des librairies
Installez les bibliothèques nécessaires à l’exécution du script :
pip3 install feedparser python-dotenv atproto
3. Fichier de configuration
Créez un fichier pour stocker vos identifiants en sécurité.
nano autobluesky.conf
Collez le contenu suivant en remplaçant les valeurs par les votre :
BLUESKY_HANDLE=votre-pseudo.bsky.social
BLUESKY_PASSWORD=xxxx-xxxx-xxxx-xxxx
(Utilisez le mot de passe en xxxx-... généré à l’Étape 1).
Étape 3 : le script (autobluesky.py)
Créez le fichier du script :
nano autobluesky.py
Collez l’intégralité du code ci-dessous.
⚠️ Important : Pensez à modifier les lignes RSS_FEED_URL, env_path et STATE_FILE au début du script pour qu’elles correspondent à votre propre dossier utilisateur (remplacez /home/maison/ par votre chemin).
#!/usr/bin/env python3
#
# Publication automatique sur BlueSky des articles
# du Blog
#
# A ajouter sur une tache crontab
# Configuration des jetons dans rss_social_env
#
import feedparser
import json
import os
import urllib.request
import re
from atproto import Client, client_utils, models
from dotenv import load_dotenv
# --- Configuration ---
env_path = os.path.expanduser('/home/maison/autobluesky.conf')
load_dotenv(env_path)
RSS_FEED_URL = "https://monblog.fr/feed"
STATE_FILE = '/home/maison/rss_state.json'
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
def load_state():
if os.path.exists(STATE_FILE):
try:
with open(STATE_FILE, 'r') as f: return json.load(f)
except: return []
return []
def save_state(published_urls):
with open(STATE_FILE, 'w') as f: json.dump(published_urls, f, indent=4)
def get_detailed_info(url):
""" Extrait l'image et le résumé en fouillant le HTML """
try:
req = urllib.request.Request(url, headers=HEADERS)
with urllib.request.urlopen(req, timeout=10) as response:
html = response.read().decode('utf-8', errors='ignore')
# Extraction Image
img_match = re.search(r'property="og:image" content="(.*?)"', html)
if not img_match:
all_imgs = re.findall(r'<img [^>]*src="([^"]*(?:jpg|jpeg|png))"', html)
img_url = next((i for i in all_imgs if not any(x in i.lower() for x in ['logo', 'avatar', 'icon'])), None)
else:
img_url = img_match.group(1)
# Extraction Résumé
desc_match = re.search(r'property="og:description" content="(.*?)"', html)
if not desc_match:
paragraphs = re.findall(r'<p>(.*?)</p>', html, re.DOTALL)
real_p = [re.sub('<[^<]+?>', '', p).strip() for p in paragraphs if len(p) > 50]
summary = real_p[0] if real_p else "Découvrez notre nouvel article sur MonBlog !"
else:
summary = desc_match.group(1)
return img_url, summary
except:
return None, "Découvrez notre nouvel article sur MonBlog !"
def main():
client = Client()
handle = os.getenv("BLUESKY_HANDLE")
password = os.getenv("BLUESKY_PASSWORD")
if not handle or not password:
return
try:
client.login(handle, password)
except Exception as e:
print(f"❌ Erreur connexion : {e}")
return
published_urls = load_state()
try:
req = urllib.request.Request(RSS_FEED_URL, headers=HEADERS)
with urllib.request.urlopen(req, timeout=15) as response:
feed = feedparser.parse(response.read())
except Exception:
return
# On ne traite que les 10 derniers billets du flux
new_entries = [e for e in feed.entries[:10] if e.link not in published_urls]
if not new_entries:
print("💤 Rien de nouveau à publier.")
return
# Publication automatique (du plus ancien au plus récent)
for entry in reversed(new_entries):
print(f"🚀 Publication auto : {entry.title}")
img_url, summary = get_detailed_info(entry.link)
try:
embed_external = None
if img_url:
try:
img_req = urllib.request.Request(img_url, headers=HEADERS)
img_data = urllib.request.urlopen(img_req).read()
if len(img_data) < 1000000:
upload = client.upload_blob(img_data)
embed_external = models.AppBskyEmbedExternal.Main(
external=models.AppBskyEmbedExternal.External(
title=entry.title,
description=summary[:200],
uri=entry.link,
thumb=upload.blob
)
)
except: pass
tb = client_utils.TextBuilder()
tb.text(f"📰 {entry.title}\n\n🔗 ")
tb.link("Lire l'article sur MonBlog", entry.link)
client.send_post(text=tb, embed=embed_external)
# On enregistre immédiatement pour éviter les doublons en cas de plantage
published_urls.append(entry.link)
save_state(published_urls)
print("✅ OK")
except Exception as e:
print(f"❌ Erreur sur {entry.title}: {e}")
if __name__ == "__main__":
main()
Rendez le script exécutable :
chmod +x autobluesky.py
Étape 4 : automatisation
Pour lancer le script automatiquement (par exemple toutes les heures), utilisez le planificateur de tâches Cron.
- Ouvrez l’éditeur cron :
crontab -e
- Ajoutez la ligne suivante à la fin du fichier :(Adaptez les chemins selon votre nom d’utilisateur)
# Lance le script toutes les heures à la minute 00
0 * * * * /home/maison/autobluesky/venv/bin/python3 /home/maison/autobluesky/autobluesky.py >> /tmp/autobluesky.log 2>&1
Note : On appelle directement l’exécutable
python3situé dans le dossiervenv. Cela permet au script d’accéder aux librairies installées sans avoir besoin d’activer manuellement l’environnement.
Script de test : test_bluesky.py
Ce script est minimaliste : il tente de se connecter et d’afficher le nom de ton profil pour confirmer que le Mot de passe d’application est valide.
⚠️ Important : Pensez à modifier les lignes pour qu’elles correspondent à votre propre dossier utilisateur (remplacez /home/maison/ par votre chemin).
#!/usr/bin/env python3
import os
from atproto import Client
from dotenv import load_dotenv
# Configuration du chemin
env_path = os.path.expanduser('/home/maison/autobluesky.conf')
load_dotenv(env_path)
def test_connection():
handle = os.getenv("BLUESKY_HANDLE")
password = os.getenv("BLUESKY_PASSWORD")
if not handle or not password:
print("❌ Erreur : Identifiants introuvables dans /home/maison/autobluesky.conf")
return
client = Client()
try:
print(f"⏳ Tentative de connexion pour {handle}...")
profile = client.login(handle, password)
print(f"✅ Connexion réussie !")
print(f"👤 Nom affiché sur le profil : {profile.display_name}")
except Exception as e:
print(f"❌ Échec de la connexion : {e}")
print("\nVérifiez votre App Password ici : https://bsky.app/settings/app-passwords")
if __name__ == "__main__":
test_connection()

