Compare commits
37 Commits
351eec4840
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baa055a36e | ||
|
|
6565c62a7a | ||
|
|
08cb7e18e3 | ||
|
|
bdeec32a25 | ||
|
|
4a83d526f9 | ||
|
|
48710862ab | ||
| 16c04ff7d3 | |||
| e3f1932775 | |||
| 05b69da411 | |||
|
|
bd79ddd40c | ||
|
|
d48646fba8 | ||
|
|
e7ac3c9b68 | ||
|
|
ffb0105a49 | ||
|
|
036b5483b3 | ||
|
|
3b0d3b6412 | ||
|
|
22b903f35b | ||
| 9fde78b048 | |||
|
|
2d7a86c2cf | ||
|
|
3b3edaf409 | ||
|
|
e4e1cf634e | ||
| 1f14a2cec4 | |||
| dc451475eb | |||
| 4a416d8468 | |||
| 4d04ba245a | |||
| e3e443c0c0 | |||
| 3fc74cb008 | |||
| 300d45192c | |||
| f11b0a1677 | |||
|
|
344b2d56a3 | ||
|
|
17219e006f | ||
|
|
ed85113d27 | ||
| dc47bf017a | |||
| 3810cef150 | |||
| 6f67822e7e | |||
| d2d05deb06 | |||
| 412136f95c | |||
| 35ba7a7a5e |
@@ -2,7 +2,7 @@ pipeline {
|
||||
agent any
|
||||
|
||||
options {
|
||||
timeout(time: 15, unit: 'MINUTES')
|
||||
timeout(time: 20, unit: 'MINUTES')
|
||||
timestamps()
|
||||
buildDiscarder(logRotator(numToKeepStr: '10'))
|
||||
disableConcurrentBuilds()
|
||||
@@ -12,10 +12,15 @@ pipeline {
|
||||
cron('H/30 * * * *')
|
||||
}
|
||||
|
||||
environment {
|
||||
VENV_DIR = 'venv'
|
||||
PIP_CACHE_DIR = "${WORKSPACE}/.pip-cache"
|
||||
PYTHONUNBUFFERED = '1'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout Code') {
|
||||
steps {
|
||||
// Pulls the code from the repository where this Jenkinsfile lives
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
@@ -23,34 +28,41 @@ pipeline {
|
||||
stage('Setup Python & Install Dependencies') {
|
||||
steps {
|
||||
sh '''
|
||||
set -e # Exit immediately if a command exits with a non-zero status
|
||||
set -euxo pipefail
|
||||
|
||||
# Create a virtual environment named 'venv'
|
||||
python3 -m venv venv
|
||||
python3 -m venv "${VENV_DIR}"
|
||||
|
||||
# Activate the virtual environment and install dependencies
|
||||
. venv/bin/activate && \
|
||||
pip install --upgrade pip && \
|
||||
pip install -U atproto tweety-ns playwright httpx arrow python-dotenv moviepy
|
||||
# Always use venv python explicitly
|
||||
"${VENV_DIR}/bin/python" -m pip install --upgrade pip wheel setuptools
|
||||
|
||||
# Check if moviepy is installed
|
||||
pip list | grep moviepy || { echo 'MoviePy installation failed!'; exit 1; }
|
||||
"${VENV_DIR}/bin/pip" install --cache-dir "${PIP_CACHE_DIR}" -U \
|
||||
atproto \
|
||||
tweety-ns \
|
||||
playwright \
|
||||
httpx \
|
||||
arrow \
|
||||
python-dotenv \
|
||||
moviepy \
|
||||
fastfeedparser \
|
||||
beautifulsoup4 \
|
||||
charset-normalizer \
|
||||
Pillow
|
||||
|
||||
# Check if FFmpeg is installed
|
||||
ffmpeg -version || { echo 'FFmpeg is not installed!'; exit 1; }
|
||||
# Verify required imports
|
||||
"${VENV_DIR}/bin/python" -c "import fastfeedparser; print('fastfeedparser OK')"
|
||||
"${VENV_DIR}/bin/python" -c "import moviepy; print('moviepy OK')"
|
||||
|
||||
# Verify that moviepy can be imported
|
||||
python3 -c "import moviepy" || { echo 'MoviePy import failed!'; exit 1; }
|
||||
# Check FFmpeg
|
||||
ffmpeg -version
|
||||
|
||||
# Install the local browser binaries for this environment
|
||||
playwright install chromium
|
||||
# Install Playwright browser binaries in this workspace environment
|
||||
"${VENV_DIR}/bin/python" -m playwright install chromium
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Run Script') {
|
||||
steps {
|
||||
// Securely injects Jenkins credentials as environment variables
|
||||
withCredentials([
|
||||
string(credentialsId: 'TWITTER_USERNAME', variable: 'TWITTER_USERNAME'),
|
||||
string(credentialsId: 'TWITTER_PASSWORD', variable: 'TWITTER_PASSWORD'),
|
||||
@@ -60,18 +72,26 @@ pipeline {
|
||||
string(credentialsId: 'BSKY_3CAT_APP_PASSWORD', variable: 'BSKY_3CAT_APP_PASSWORD')
|
||||
]) {
|
||||
sh '''
|
||||
# Activate the virtual environment and run the script
|
||||
. venv/bin/activate && \
|
||||
python3 twitter2bsky_daemon.py \
|
||||
set -euxo pipefail
|
||||
|
||||
"${VENV_DIR}/bin/python" twitter2bsky_daemon.py \
|
||||
--twitter-username "$TWITTER_USERNAME" \
|
||||
--twitter-password "$TWITTER_PASSWORD" \
|
||||
--twitter-email "$TWITTER_3CAT_EMAIL" \
|
||||
--twitter-handle "$TWITTER_3CAT_HANDLE" \
|
||||
--bsky-handle "$BSKY_3CAT_HANDLE" \
|
||||
--bsky-password "$BSKY_3CAT_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_3CAT_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
// Optional: keep logs/artifacts if your script writes any
|
||||
archiveArtifacts artifacts: '*.log, *.json', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_FCB_EMAIL" \
|
||||
--twitter-handle "$TWITTER_FCBFUTBOLSALA_HANDLE" \
|
||||
--bsky-handle "$BSKY_MQUB_HANDLE" \
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD"
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_FCB_EMAIL" \
|
||||
--twitter-handle "$TWITTER_FCBHANDBOL_HANDLE" \
|
||||
--bsky-handle "$BSKY_MQUB_HANDLE" \
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD"
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_FCB_EMAIL" \
|
||||
--twitter-handle "$TWITTER_FCBHOQUEI_HANDLE" \
|
||||
--bsky-handle "$BSKY_MQUB_HANDLE" \
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD"
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_FCB_EMAIL" \
|
||||
--twitter-handle "$TWITTER_FCBMASIA_HANDLE" \
|
||||
--bsky-handle "$BSKY_MQUB_HANDLE" \
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD"
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
options {
|
||||
timeout(time: 15, unit: 'MINUTES')
|
||||
timestamps()
|
||||
buildDiscarder(logRotator(numToKeepStr: '10'))
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
triggers {
|
||||
cron('H/30 * * * *')
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout Code') {
|
||||
steps {
|
||||
// Pulls the code from the repository where this Jenkinsfile lives
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Setup Python & Install Dependencies') {
|
||||
steps {
|
||||
sh '''
|
||||
set -e # Exit immediately if a command exits with a non-zero status
|
||||
|
||||
# Create a virtual environment named 'venv'
|
||||
python3 -m venv venv
|
||||
|
||||
# Activate the virtual environment and install dependencies
|
||||
. venv/bin/activate && \
|
||||
pip install --upgrade pip && \
|
||||
pip install -U atproto tweety-ns playwright httpx arrow python-dotenv moviepy
|
||||
|
||||
# Check if moviepy is installed
|
||||
pip list | grep moviepy || { echo 'MoviePy installation failed!'; exit 1; }
|
||||
|
||||
# Check if FFmpeg is installed
|
||||
ffmpeg -version || { echo 'FFmpeg is not installed!'; exit 1; }
|
||||
|
||||
# Verify that moviepy can be imported
|
||||
python3 -c "import moviepy" || { echo 'MoviePy import failed!'; exit 1; }
|
||||
|
||||
# Install the local browser binaries for this environment
|
||||
playwright install chromium
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Run Script') {
|
||||
steps {
|
||||
// Securely injects Jenkins credentials as environment variables
|
||||
withCredentials([
|
||||
string(credentialsId: 'TWITTER_USERNAME', variable: 'TWITTER_USERNAME'),
|
||||
string(credentialsId: 'TWITTER_PASSWORD', variable: 'TWITTER_PASSWORD'),
|
||||
string(credentialsId: 'TWITTER_VERSIORAC1_EMAIL', variable: 'TWITTER_VERSIORAC1_EMAIL'),
|
||||
string(credentialsId: 'TWITTER_VERSIORAC1_HANDLE', variable: 'TWITTER_VERSIORAC1_HANDLE'),
|
||||
string(credentialsId: 'BSKY_VERSIORAC1_HANDLE', variable: 'BSKY_VERSIORAC1_HANDLE'),
|
||||
string(credentialsId: 'BSKY_VERSIORAC1_APP_PASSWORD', variable: 'BSKY_VERSIORAC1_APP_PASSWORD')
|
||||
]) {
|
||||
sh '''
|
||||
# Activate the virtual environment and run the script
|
||||
. venv/bin/activate && \
|
||||
python3 twitter2bsky_daemon.py \
|
||||
--twitter-username "$TWITTER_USERNAME" \
|
||||
--twitter-password "$TWITTER_PASSWORD" \
|
||||
--twitter-email "$TWITTER_VERSIORAC1_EMAIL" \
|
||||
--twitter-handle "$TWITTER_VERSIORAC1_HANDLE" \
|
||||
--bsky-handle "$BSKY_VERSIORAC1_HANDLE" \
|
||||
--bsky-password "$BSKY_VERSIORAC1_APP_PASSWORD"
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_APM_EMAIL" \
|
||||
--twitter-handle "$TWITTER_APM_HANDLE" \
|
||||
--bsky-handle "$BSKY_APM_HANDLE" \
|
||||
--bsky-password "$BSKY_APM_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_APM_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_BETEVE_EMAIL" \
|
||||
--twitter-handle "$TWITTER_BETEVE_HANDLE" \
|
||||
--bsky-handle "$BSKY_BETEVE_HANDLE" \
|
||||
--bsky-password "$BSKY_BETEVE_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_BETEVE_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_CAT112_EMAIL" \
|
||||
--twitter-handle "$TWITTER_BOMBERSCAT_HANDLE" \
|
||||
--bsky-handle "$BSKY_CAT112_HANDLE" \
|
||||
--bsky-password "$BSKY_CAT112_PASSWORD"
|
||||
--bsky-password "$BSKY_CAT112_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
options {
|
||||
timeout(time: 15, unit: 'MINUTES')
|
||||
timestamps()
|
||||
buildDiscarder(logRotator(numToKeepStr: '10'))
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
triggers {
|
||||
cron('H/30 * * * *')
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout Code') {
|
||||
steps {
|
||||
// Pulls the code from the repository where this Jenkinsfile lives
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Setup Python & Install Dependencies') {
|
||||
steps {
|
||||
sh '''
|
||||
set -e # Exit immediately if a command exits with a non-zero status
|
||||
|
||||
# Create a virtual environment named 'venv'
|
||||
python3 -m venv venv
|
||||
|
||||
# Activate the virtual environment and install dependencies
|
||||
. venv/bin/activate && \
|
||||
pip install --upgrade pip && \
|
||||
pip install -U atproto tweety-ns playwright httpx arrow python-dotenv moviepy
|
||||
|
||||
# Check if moviepy is installed
|
||||
pip list | grep moviepy || { echo 'MoviePy installation failed!'; exit 1; }
|
||||
|
||||
# Check if FFmpeg is installed
|
||||
ffmpeg -version || { echo 'FFmpeg is not installed!'; exit 1; }
|
||||
|
||||
# Verify that moviepy can be imported
|
||||
python3 -c "import moviepy" || { echo 'MoviePy import failed!'; exit 1; }
|
||||
|
||||
# Install the local browser binaries for this environment
|
||||
playwright install chromium
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Run Script') {
|
||||
steps {
|
||||
// Securely injects Jenkins credentials as environment variables
|
||||
withCredentials([
|
||||
string(credentialsId: 'TWITTER_USERNAME', variable: 'TWITTER_USERNAME'),
|
||||
string(credentialsId: 'TWITTER_PASSWORD', variable: 'TWITTER_PASSWORD'),
|
||||
string(credentialsId: 'TWITTER_BETEVE_EMAIL', variable: 'TWITTER_BETEVE_EMAIL'),
|
||||
string(credentialsId: 'TWITTER_BTVBASICS_HANDLE', variable: 'TWITTER_BTVBASICS_HANDLE'),
|
||||
string(credentialsId: 'BSKY_BETEVE_HANDLE', variable: 'BSKY_BETEVE_HANDLE'),
|
||||
string(credentialsId: 'BSKY_BETEVE_APP_PASSWORD', variable: 'BSKY_BETEVE_APP_PASSWORD')
|
||||
]) {
|
||||
sh '''
|
||||
# Activate the virtual environment and run the script
|
||||
. venv/bin/activate && \
|
||||
python3 twitter2bsky_daemon.py \
|
||||
--twitter-username "$TWITTER_USERNAME" \
|
||||
--twitter-password "$TWITTER_PASSWORD" \
|
||||
--twitter-email "$TWITTER_BETEVE_EMAIL" \
|
||||
--twitter-handle "$TWITTER_BTVBASICS_HANDLE" \
|
||||
--bsky-handle "$BSKY_BETEVE_HANDLE" \
|
||||
--bsky-password "$BSKY_BETEVE_APP_PASSWORD"
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_BETEVE_EMAIL" \
|
||||
--twitter-handle "$TWITTER_BTVESPORTS_HANDLE" \
|
||||
--bsky-handle "$BSKY_BETEVE_HANDLE" \
|
||||
--bsky-password "$BSKY_BETEVE_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_BETEVE_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_BETEVE_EMAIL" \
|
||||
--twitter-handle "$TWITTER_BTVLLENGUA_HANDLE" \
|
||||
--bsky-handle "$BSKY_BETEVE_HANDLE" \
|
||||
--bsky-password "$BSKY_BETEVE_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_BETEVE_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_BETEVE_EMAIL" \
|
||||
--twitter-handle "$TWITTER_BTVNOTICIES_HANDLE" \
|
||||
--bsky-handle "$BSKY_BETEVE_HANDLE" \
|
||||
--bsky-password "$BSKY_BETEVE_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_BETEVE_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ pipeline {
|
||||
"https://cr-news-api-service.prd.crunchyrollsvc.com/v1/es-ES/rss" \
|
||||
"$BSKY_CRUNCHYROLL_HANDLE" \
|
||||
"$TWITTER_CRUNCHY_EMAIL" \
|
||||
"$BSKY_CRUNCHYROLL_APP_PASSWORD"
|
||||
"$BSKY_CRUNCHYROLL_APP_PASSWORD" \
|
||||
--service "https://eurosky.social"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_CRUNCHY_EMAIL" \
|
||||
--twitter-handle "$TWITTER_CRUNCHYROLL_HANDLE" \
|
||||
--bsky-handle "$BSKY_CRUNCHYROLL_HANDLE" \
|
||||
--bsky-password "$BSKY_CRUNCHYROLL_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_CRUNCHYROLL_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
148
jenkins/ePrss
148
jenkins/ePrss
@@ -2,94 +2,148 @@ pipeline {
|
||||
agent any
|
||||
|
||||
options {
|
||||
timeout(time: 15, unit: 'MINUTES')
|
||||
timeout(time: 30, unit: 'MINUTES')
|
||||
timestamps()
|
||||
buildDiscarder(logRotator(numToKeepStr: '10'))
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
triggers {
|
||||
cron('H */6 * * *')
|
||||
cron('''
|
||||
H 22,10,16 * * *
|
||||
0 4 * * *
|
||||
''')
|
||||
}
|
||||
|
||||
|
||||
environment {
|
||||
VENV_DIR = 'venv'
|
||||
MAX_PARALLEL_FEEDS = '4'
|
||||
JITTER_MAX_SECONDS = '12'
|
||||
PYTHONUNBUFFERED = '1'
|
||||
PIP_CACHE_DIR = "${WORKSPACE}/.pip-cache"
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout & Setup') {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Setup Python') {
|
||||
steps {
|
||||
sh '''
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
pip install atproto fastfeedparser beautifulsoup4 httpx arrow charset-normalizer Pillow
|
||||
set -euxo pipefail
|
||||
python3 -m venv "${VENV_DIR}"
|
||||
. "${VENV_DIR}/bin/activate"
|
||||
python -m pip install --upgrade pip wheel setuptools
|
||||
pip install --cache-dir "${PIP_CACHE_DIR}" \
|
||||
atproto fastfeedparser beautifulsoup4 httpx arrow charset-normalizer Pillow
|
||||
python --version
|
||||
pip --version
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Process All RSS Feeds') {
|
||||
stage('Process All RSS Feeds (Batched Parallel)') {
|
||||
steps {
|
||||
// 🔐 Fetch the single set of EP credentials ONCE for all 26 feeds
|
||||
withCredentials([
|
||||
string(credentialsId: 'BSKY_EP_HANDLE', variable: 'BSKY_EP_HANDLE'),
|
||||
string(credentialsId: 'BSKY_EP_USERNAME', variable: 'BSKY_EP_USERNAME'),
|
||||
string(credentialsId: 'BSKY_EP_APP_PASSWORD', variable: 'BSKY_EP_APP_PASSWORD')
|
||||
]) {
|
||||
script {
|
||||
// 📍 The map only contains the hardcoded URLs
|
||||
def feeds = [
|
||||
// Original 19 Feeds
|
||||
'badalona': 'https://www.elperiodico.cat/ca/rss/badalona/rss.xml',
|
||||
'barcelona': 'https://www.elperiodico.cat/ca/rss/barcelona/rss.xml',
|
||||
'ciencia': 'https://www.elperiodico.cat/ca/rss/ciencia/rss.xml',
|
||||
'cornella': 'https://www.elperiodico.cat/ca/rss/cornella/rss.xml',
|
||||
'economia': 'https://www.elperiodico.cat/ca/rss/economia/rss.xml',
|
||||
'educacio': 'https://www.elperiodico.cat/ca/rss/educacio/rss.xml',
|
||||
'esports': 'https://www.elperiodico.cat/ca/rss/esports/rss.xml',
|
||||
'extra': 'https://www.elperiodico.cat/ca/rss/extra/rss.xml',
|
||||
'gent': 'https://www.elperiodico.cat/ca/rss/gent/rss.xml',
|
||||
'hospitalet': 'https://www.elperiodico.cat/ca/rss/hospitalet/rss.xml',
|
||||
'internacional': 'https://www.elperiodico.cat/ca/rss/internacional/rss.xml',
|
||||
'medi-ambient': 'https://www.elperiodico.cat/ca/rss/medi-ambient/rss.xml',
|
||||
'motor': 'https://www.elperiodico.cat/ca/rss/motor/rss.xml',
|
||||
'oci-i-cultura': 'https://www.elperiodico.cat/ca/rss/oci-i-cultura/rss.xml',
|
||||
'opinio': 'https://www.elperiodico.cat/ca/rss/opinio/rss.xml',
|
||||
'politica': 'https://www.elperiodico.cat/ca/rss/politica/rss.xml',
|
||||
'portada': 'https://www.elperiodico.cat/ca/rss/portada/rss.xml',
|
||||
'que-fer': 'https://www.elperiodico.cat/ca/rss/que-fer/rss.xml',
|
||||
'sabadell': 'https://www.elperiodico.cat/ca/rss/sabadell/rss.xml',
|
||||
|
||||
// 7 New Feeds
|
||||
'sanitat': 'https://www.elperiodico.cat/ca/rss/sanitat/rss.xml',
|
||||
'santa-coloma': 'https://www.elperiodico.cat/ca/rss/santa-coloma/rss.xml',
|
||||
'societat': 'https://www.elperiodico.cat/ca/rss/societat/rss.xml',
|
||||
'tecnologia': 'https://www.elperiodico.cat/ca/rss/tecnologia/rss.xml',
|
||||
'tele': 'https://www.elperiodico.cat/ca/rss/tele/rss.xml',
|
||||
'temps': 'https://www.elperiodico.cat/ca/rss/temps/rss.xml',
|
||||
'terrassa': 'https://www.elperiodico.cat/ca/rss/terrassa/rss.xml'
|
||||
[name: 'badalona', url: 'https://www.elperiodico.cat/ca/rss/badalona/rss.xml'],
|
||||
[name: 'barcelona', url: 'https://www.elperiodico.cat/ca/rss/barcelona/rss.xml'],
|
||||
[name: 'ciencia', url: 'https://www.elperiodico.cat/ca/rss/ciencia/rss.xml'],
|
||||
[name: 'cornella', url: 'https://www.elperiodico.cat/ca/rss/cornella/rss.xml'],
|
||||
[name: 'economia', url: 'https://www.elperiodico.cat/ca/rss/economia/rss.xml'],
|
||||
[name: 'educacio', url: 'https://www.elperiodico.cat/ca/rss/educacio/rss.xml'],
|
||||
[name: 'esports', url: 'https://www.elperiodico.cat/ca/rss/esports/rss.xml'],
|
||||
[name: 'extra', url: 'https://www.elperiodico.cat/ca/rss/extra/rss.xml'],
|
||||
[name: 'gent', url: 'https://www.elperiodico.cat/ca/rss/gent/rss.xml'],
|
||||
[name: 'hospitalet', url: 'https://www.elperiodico.cat/ca/rss/hospitalet/rss.xml'],
|
||||
[name: 'internacional', url: 'https://www.elperiodico.cat/ca/rss/internacional/rss.xml'],
|
||||
[name: 'medi-ambient', url: 'https://www.elperiodico.cat/ca/rss/medi-ambient/rss.xml'],
|
||||
[name: 'motor', url: 'https://www.elperiodico.cat/ca/rss/motor/rss.xml'],
|
||||
[name: 'oci-i-cultura', url: 'https://www.elperiodico.cat/ca/rss/oci-i-cultura/rss.xml'],
|
||||
[name: 'opinio', url: 'https://www.elperiodico.cat/ca/rss/opinio/rss.xml'],
|
||||
[name: 'politica', url: 'https://www.elperiodico.cat/ca/rss/politica/rss.xml'],
|
||||
[name: 'portada', url: 'https://www.elperiodico.cat/ca/rss/portada/rss.xml'],
|
||||
[name: 'que-fer', url: 'https://www.elperiodico.cat/ca/rss/que-fer/rss.xml'],
|
||||
[name: 'sabadell', url: 'https://www.elperiodico.cat/ca/rss/sabadell/rss.xml'],
|
||||
[name: 'sanitat', url: 'https://www.elperiodico.cat/ca/rss/sanitat/rss.xml'],
|
||||
[name: 'santa-coloma', url: 'https://www.elperiodico.cat/ca/rss/santa-coloma/rss.xml'],
|
||||
[name: 'societat', url: 'https://www.elperiodico.cat/ca/rss/societat/rss.xml'],
|
||||
[name: 'tecnologia', url: 'https://www.elperiodico.cat/ca/rss/tecnologia/rss.xml'],
|
||||
[name: 'tele', url: 'https://www.elperiodico.cat/ca/rss/tele/rss.xml'],
|
||||
[name: 'temps', url: 'https://www.elperiodico.cat/ca/rss/temps/rss.xml'],
|
||||
[name: 'terrassa', url: 'https://www.elperiodico.cat/ca/rss/terrassa/rss.xml']
|
||||
]
|
||||
|
||||
int batchSize = env.MAX_PARALLEL_FEEDS as int
|
||||
int jitterMax = env.JITTER_MAX_SECONDS as int
|
||||
def batches = feeds.collate(batchSize)
|
||||
|
||||
echo "Total feeds: ${feeds.size()}, batch size: ${batchSize}, batches: ${batches.size()}"
|
||||
|
||||
int batchNum = 0
|
||||
for (def batch : batches) {
|
||||
batchNum++
|
||||
echo "Starting batch ${batchNum}/${batches.size()} with ${batch.size()} feeds"
|
||||
|
||||
def parallelTasks = [:]
|
||||
|
||||
feeds.each { feedName, feedUrl ->
|
||||
parallelTasks[feedName] = {
|
||||
stage(feedName.capitalize()) {
|
||||
sh """
|
||||
. venv/bin/activate
|
||||
batch.each { feed ->
|
||||
def feedName = feed.name
|
||||
def feedUrl = feed.url
|
||||
|
||||
parallelTasks[feedName] = {
|
||||
stage("Feed: ${feedName}") {
|
||||
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
|
||||
sh """
|
||||
set -euxo pipefail
|
||||
. "${VENV_DIR}/bin/activate"
|
||||
|
||||
JITTER=\$((RANDOM % ${jitterMax}))
|
||||
echo "[${feedName}] sleeping \$JITTER s jitter"
|
||||
sleep \$JITTER
|
||||
|
||||
# The hardcoded URL and updated EP credentials are used here
|
||||
python3 rss2bsky.py \\
|
||||
"${feedUrl}" \\
|
||||
"\$BSKY_EP_HANDLE" \\
|
||||
"\$BSKY_EP_USERNAME" \\
|
||||
"\$BSKY_EP_APP_PASSWORD"
|
||||
"\$BSKY_EP_APP_PASSWORD" \
|
||||
--service "https://eurosky.social"
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute all 26 feeds simultaneously
|
||||
parallel parallelTasks
|
||||
echo "Finished batch ${batchNum}/${batches.size()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
archiveArtifacts artifacts: '*.json, **/*.log', allowEmptyArchive: true
|
||||
}
|
||||
unstable {
|
||||
echo 'Build unstable: one or more feeds failed, but pipeline completed.'
|
||||
}
|
||||
failure {
|
||||
echo 'Build failed.'
|
||||
}
|
||||
success {
|
||||
echo 'Build succeeded.'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_RAC1_EMAIL" \
|
||||
--twitter-handle "$TWITTER_ELMONARAC1_HANDLE" \
|
||||
--bsky-handle "$BSKY_RAC1_HANDLE" \
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_CAT112_EMAIL" \
|
||||
--twitter-handle "$TWITTER_EMERGENCIESCAT_HANDLE" \
|
||||
--bsky-handle "$BSKY_CAT112_HANDLE" \
|
||||
--bsky-password "$BSKY_CAT112_PASSWORD"
|
||||
--bsky-password "$BSKY_CAT112_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_ESPORT3_EMAIL" \
|
||||
--twitter-handle "$TWITTER_ESPORT3_HANDLE" \
|
||||
--bsky-handle "$BSKY_ESPORT3_HANDLE" \
|
||||
--bsky-password "$BSKY_ESPORT3_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_ESPORT3_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_RAC1_EMAIL" \
|
||||
--twitter-handle "$TWITTER_ESPORTSRAC1_HANDLE" \
|
||||
--bsky-handle "$BSKY_RAC1_HANDLE" \
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_LAXARXAMES_EMAIL" \
|
||||
--twitter-handle "$TWITTER_ESPORTENXARXA_HANDLE" \
|
||||
--bsky-handle "$BSKY_LAXARXAMES_HANDLE" \
|
||||
--bsky-password "$BSKY_LAXARXAMES_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_LAXARXAMES_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_RAC1_EMAIL" \
|
||||
--twitter-handle "$TWITTER_FCBAC1_HANDLE" \
|
||||
--bsky-handle "$BSKY_RAC1_HANDLE" \
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_FCB_EMAIL" \
|
||||
--twitter-handle "$TWITTER_FCB_HANDLE" \
|
||||
--bsky-handle "$BSKY_MQUB_HANDLE" \
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD"
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_FCB_EMAIL" \
|
||||
--twitter-handle "$TWITTER_FCBATLETIC_HANDLE" \
|
||||
--bsky-handle "$BSKY_MQUB_HANDLE" \
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD"
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_FCB_EMAIL" \
|
||||
--twitter-handle "$TWITTER_FCBBASKET_HANDLE" \
|
||||
--bsky-handle "$BSKY_MQUB_HANDLE" \
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD"
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_FCB_EMAIL" \
|
||||
--twitter-handle "$TWITTER_FCBFEMENI_HANDLE" \
|
||||
--bsky-handle "$BSKY_MQUB_HANDLE" \
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD"
|
||||
--bsky-password "$BSKY_MQUB_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_RAC1_EMAIL" \
|
||||
--twitter-handle "$TWITTER_LACOMPETENCIARAC1_HANDLE" \
|
||||
--bsky-handle "$BSKY_RAC1_HANDLE" \
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_LAXARXAMES_EMAIL" \
|
||||
--twitter-handle "$TWITTER_LAXARXAMES_HANDLE" \
|
||||
--bsky-handle "$BSKY_LAXARXAMES_HANDLE" \
|
||||
--bsky-password "$BSKY_LAXARXAMES_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_LAXARXAMES_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_RAC1_EMAIL" \
|
||||
--twitter-handle "$TWITTER_METEORAC1_HANDLE" \
|
||||
--bsky-handle "$BSKY_RAC1_HANDLE" \
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_METEOCAT_EMAIL" \
|
||||
--twitter-handle "$TWITTER_METEOCAT_HANDLE" \
|
||||
--bsky-handle "$BSKY_METEOCAT_HANDLE" \
|
||||
--bsky-password "$BSKY_METEOCAT_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_METEOCAT_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_CAT112_EMAIL" \
|
||||
--twitter-handle "$TWITTER_MOSSOS_HANDLE" \
|
||||
--bsky-handle "$BSKY_CAT112_HANDLE" \
|
||||
--bsky-password "$BSKY_CAT112_PASSWORD"
|
||||
--bsky-password "$BSKY_CAT112_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_RAC1_EMAIL" \
|
||||
--twitter-handle "$TWITTER_NOHOSERAC1_HANDLE" \
|
||||
--bsky-handle "$BSKY_RAC1_HANDLE" \
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_OPTIMOT_EMAIL" \
|
||||
--twitter-handle "$TWITTER_OPTIMOT_HANDLE" \
|
||||
--bsky-handle "$BSKY_OPTIMOT_HANDLE" \
|
||||
--bsky-password "$BSKY_OPTIMOT_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_OPTIMOT_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_3CAT_EMAIL" \
|
||||
--twitter-handle "$TWITTER_POLONIA3CAT_HANDLE" \
|
||||
--bsky-handle "$BSKY_3CAT_HANDLE" \
|
||||
--bsky-password "$BSKY_3CAT_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_3CAT_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ pipeline {
|
||||
"https://www.rac1.cat/rss/home.xml" \\
|
||||
"$BSKY_RAC1_HANDLE" \\
|
||||
"$BSKY_RAC1_USERNAME" \\
|
||||
"$BSKY_RAC1_APP_PASSWORD"
|
||||
"$BSKY_RAC1_APP_PASSWORD" \
|
||||
--service "https://eurosky.social"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_CAT112_EMAIL" \
|
||||
--twitter-handle "$TWITTER_SEMGENCAT_HANDLE" \
|
||||
--bsky-handle "$BSKY_CAT112_HANDLE" \
|
||||
--bsky-password "$BSKY_CAT112_PASSWORD"
|
||||
--bsky-password "$BSKY_CAT112_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_CAT112_EMAIL" \
|
||||
--twitter-handle "$TWITTER_TRANSIT_HANDLE" \
|
||||
--bsky-handle "$BSKY_CAT112_HANDLE" \
|
||||
--bsky-password "$BSKY_CAT112_PASSWORD"
|
||||
--bsky-password "$BSKY_CAT112_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_RAC1_EMAIL" \
|
||||
--twitter-handle "$TWITTER_VERSIORAC1_HANDLE" \
|
||||
--bsky-handle "$BSKY_RAC1_HANDLE" \
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ pipeline {
|
||||
--twitter-email "$TWITTER_RAC1_EMAIL" \
|
||||
--twitter-handle "$TWITTER_VIALLIURERAC1_HANDLE" \
|
||||
--bsky-handle "$BSKY_RAC1_HANDLE" \
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD"
|
||||
--bsky-password "$BSKY_RAC1_APP_PASSWORD" \
|
||||
--bsky-base-url https://eurosky.social
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
1354
rss2bsky.py
1354
rss2bsky.py
File diff suppressed because it is too large
Load Diff
212
testlogin.py
212
testlogin.py
@@ -1,46 +1,200 @@
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
|
||||
import httpx
|
||||
from atproto import Client
|
||||
|
||||
# --- Logging ---
|
||||
LOG_PATH = "rss2bsky_test.log"
|
||||
LOG_PATH = "bsky_login_test.log"
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s %(message)s",
|
||||
filename=LOG_PATH,
|
||||
encoding="utf-8",
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
handlers=[
|
||||
logging.FileHandler(LOG_PATH, encoding="utf-8"),
|
||||
logging.StreamHandler(),
|
||||
],
|
||||
level=logging.INFO,
|
||||
)
|
||||
|
||||
def main():
|
||||
# --- Parse command-line arguments ---
|
||||
parser = argparse.ArgumentParser(description="Post RSS to Bluesky.")
|
||||
parser.add_argument("rss_feed", help="RSS feed URL")
|
||||
parser.add_argument("bsky_handle", help="Bluesky handle")
|
||||
parser.add_argument("bsky_username", help="Bluesky username")
|
||||
parser.add_argument("bsky_app_password", help="Bluesky app password")
|
||||
parser.add_argument("--service", default="https://bsky.social", help="Bluesky server URL (default: https://bsky.social)")
|
||||
EXIT_OK = 0
|
||||
EXIT_BAD_CREDS = 2
|
||||
EXIT_RATE_LIMIT = 3
|
||||
EXIT_NETWORK = 4
|
||||
EXIT_OTHER = 5
|
||||
|
||||
args = parser.parse_args()
|
||||
bsky_username = args.bsky_username
|
||||
bsky_password = args.bsky_app_password
|
||||
service_url = args.service
|
||||
|
||||
# --- Login ---
|
||||
# SOLUCIÓ: Passem el base_url directament al constructor del Client
|
||||
client = Client(base_url=service_url)
|
||||
|
||||
backoff = 60
|
||||
while True:
|
||||
def parse_wait_seconds_from_exception(exc, default_delay=15, max_delay=900):
|
||||
"""
|
||||
Parse common rate-limit headers from atproto exceptions:
|
||||
- retry-after (seconds)
|
||||
- x-ratelimit-after (seconds)
|
||||
- ratelimit-reset (unix timestamp)
|
||||
"""
|
||||
try:
|
||||
logging.info(f"Attempting login to server: {service_url} with user: {bsky_username}")
|
||||
client.login(bsky_username, bsky_password)
|
||||
logging.info(f"Login successful for user: {bsky_username}")
|
||||
break
|
||||
headers = getattr(exc, "headers", None) or {}
|
||||
|
||||
retry_after = headers.get("retry-after") or headers.get("Retry-After")
|
||||
if retry_after:
|
||||
return min(max(int(retry_after), 1), max_delay)
|
||||
|
||||
x_after = headers.get("x-ratelimit-after") or headers.get("X-RateLimit-After")
|
||||
if x_after:
|
||||
return min(max(int(x_after), 1), max_delay)
|
||||
|
||||
reset = headers.get("ratelimit-reset") or headers.get("RateLimit-Reset")
|
||||
if reset:
|
||||
wait_s = max(int(reset) - int(time.time()) + 1, 1)
|
||||
return min(wait_s, max_delay)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return default_delay
|
||||
|
||||
|
||||
def classify_error(exc):
|
||||
"""
|
||||
Classify exception into:
|
||||
- rate_limit
|
||||
- bad_creds
|
||||
- network
|
||||
- other
|
||||
"""
|
||||
text = repr(exc).lower()
|
||||
status_code = getattr(exc, "status_code", None)
|
||||
|
||||
if status_code == 429 or "429" in text or "too many requests" in text or "ratelimit" in text:
|
||||
return "rate_limit"
|
||||
|
||||
if status_code in (401, 403) or "invalid identifier or password" in text or "authentication" in text:
|
||||
return "bad_creds"
|
||||
|
||||
transient_signals = [
|
||||
"timeout",
|
||||
"connecterror",
|
||||
"remoteprotocolerror",
|
||||
"readtimeout",
|
||||
"writetimeout",
|
||||
"503",
|
||||
"502",
|
||||
"504",
|
||||
"connection",
|
||||
]
|
||||
if any(sig in text for sig in transient_signals):
|
||||
return "network"
|
||||
|
||||
return "other"
|
||||
|
||||
|
||||
def preflight_health(service_url, timeout=8):
|
||||
url = f"{service_url.rstrip('/')}/xrpc/_health"
|
||||
try:
|
||||
r = httpx.get(url, timeout=timeout)
|
||||
logging.info(f"🩺 Health check {url} -> HTTP {r.status_code}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.exception("Login exception")
|
||||
time.sleep(backoff)
|
||||
backoff = min(backoff + 60, 600)
|
||||
logging.warning(f"🩺 Health check failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def build_client(service_url):
|
||||
normalized = service_url.strip().rstrip("/")
|
||||
|
||||
try:
|
||||
return Client(base_url=normalized)
|
||||
except TypeError:
|
||||
logging.warning("⚠️ Client(base_url=...) unsupported in this atproto version. Falling back.")
|
||||
c = Client()
|
||||
try:
|
||||
if hasattr(c, "base_url"):
|
||||
c.base_url = normalized
|
||||
elif hasattr(c, "_base_url"):
|
||||
c._base_url = normalized
|
||||
except Exception as e:
|
||||
logging.warning(f"⚠️ Could not apply custom base URL: {e}")
|
||||
return c
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Bluesky login test only.")
|
||||
parser.add_argument("--bsky-handle", required=True, help="Bluesky handle (e.g. user.example.social)")
|
||||
parser.add_argument(
|
||||
"--bsky-app-password",
|
||||
default=None,
|
||||
help="Bluesky app password (prefer env BSKY_APP_PASSWORD)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--service",
|
||||
default="https://bsky.social",
|
||||
help="PDS base URL (default: https://bsky.social)",
|
||||
)
|
||||
parser.add_argument("--max-attempts", type=int, default=3, help="Retry attempts (default: 3)")
|
||||
parser.add_argument("--base-delay", type=int, default=10, help="Base retry delay in seconds (default: 10)")
|
||||
parser.add_argument("--jitter-max", type=float, default=2.0, help="Random jitter max seconds (default: 2.0)")
|
||||
args = parser.parse_args()
|
||||
|
||||
handle = args.bsky_handle.strip()
|
||||
service_url = args.service.strip().rstrip("/")
|
||||
app_password = (args.bsky_app_password or os.getenv("BSKY_APP_PASSWORD", "")).strip()
|
||||
|
||||
if not app_password:
|
||||
logging.error("❌ Missing app password. Use --bsky-app-password or env BSKY_APP_PASSWORD.")
|
||||
print("LOGIN_FAILED_BAD_CREDS")
|
||||
sys.exit(EXIT_BAD_CREDS)
|
||||
|
||||
logging.info(f"🔐 Testing login against: {service_url}")
|
||||
logging.info(f"👤 Handle: {handle}")
|
||||
|
||||
# Optional but useful diagnostics
|
||||
preflight_health(service_url)
|
||||
|
||||
client = build_client(service_url)
|
||||
|
||||
last_kind = "other"
|
||||
|
||||
for attempt in range(1, args.max_attempts + 1):
|
||||
try:
|
||||
logging.info(f"➡️ Login attempt {attempt}/{args.max_attempts}")
|
||||
client.login(handle, app_password)
|
||||
logging.info("✅ Login successful.")
|
||||
print("LOGIN_OK")
|
||||
sys.exit(EXIT_OK)
|
||||
|
||||
except Exception as e:
|
||||
last_kind = classify_error(e)
|
||||
logging.exception(f"❌ Login failed [{last_kind}]")
|
||||
|
||||
if last_kind == "bad_creds":
|
||||
print("LOGIN_FAILED_BAD_CREDS")
|
||||
sys.exit(EXIT_BAD_CREDS)
|
||||
|
||||
if attempt >= args.max_attempts:
|
||||
break
|
||||
|
||||
if last_kind == "rate_limit":
|
||||
wait_s = parse_wait_seconds_from_exception(e, default_delay=args.base_delay)
|
||||
elif last_kind == "network":
|
||||
wait_s = min(args.base_delay * attempt, 60)
|
||||
else:
|
||||
wait_s = min(args.base_delay * attempt, 45)
|
||||
|
||||
wait_s = wait_s + random.uniform(0, max(args.jitter_max, 0.0))
|
||||
logging.warning(f"⏳ Waiting {wait_s:.1f}s before retry...")
|
||||
time.sleep(wait_s)
|
||||
|
||||
if last_kind == "rate_limit":
|
||||
print("LOGIN_FAILED_RATE_LIMIT")
|
||||
sys.exit(EXIT_RATE_LIMIT)
|
||||
if last_kind == "network":
|
||||
print("LOGIN_FAILED_NETWORK")
|
||||
sys.exit(EXIT_NETWORK)
|
||||
|
||||
print("LOGIN_FAILED")
|
||||
sys.exit(EXIT_OTHER)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user