From 793a56af58d3a011576504424d693f5c05823a55 Mon Sep 17 00:00:00 2001 From: Guillem Hernandez Sola Date: Fri, 10 Apr 2026 19:58:39 +0000 Subject: [PATCH] Fixes for 429 - fix SyntaxError caused by global STATE_PATH/COOLDOWN_STATE_PATH usage in main() - pass state and cooldown paths explicitly to helpers - add emoji-based logging for login, posting, cooldown, and summary events - keep shared cooldown behavior across parallel Jenkins workers --- rss2bsky.py | 241 +++++++++++++++++++++++++--------------------------- 1 file changed, 115 insertions(+), 126 deletions(-) diff --git a/rss2bsky.py b/rss2bsky.py index 5b07a61..b5c2acb 100644 --- a/rss2bsky.py +++ b/rss2bsky.py @@ -25,8 +25,8 @@ except ImportError: # --- Configuration --- -STATE_PATH = "rss2bsky_state.json" -COOLDOWN_STATE_PATH = "rss2bsky_cooldowns.json" +DEFAULT_STATE_PATH = "rss2bsky_state.json" +DEFAULT_COOLDOWN_STATE_PATH = "rss2bsky_cooldowns.json" DEDUPE_BSKY_LIMIT = 30 BSKY_TEXT_MAX_LENGTH = 275 @@ -56,7 +56,7 @@ logging.basicConfig( ) if not PIL_AVAILABLE: - logging.warning("Pillow is not installed. External card thumbnail compression is disabled.") + logging.warning("๐ŸŸก Pillow is not installed. External card thumbnail compression is disabled.") # --- Cooldown persistence --- @@ -69,7 +69,7 @@ def default_cooldown_state(): } -def load_cooldown_state(path=COOLDOWN_STATE_PATH): +def load_cooldown_state(path): if not os.path.exists(path): return default_cooldown_state() @@ -86,11 +86,11 @@ def load_cooldown_state(path=COOLDOWN_STATE_PATH): state.setdefault("updated_at", None) return state except Exception as e: - logging.warning(f"Could not load cooldown state {path}: {e}") + logging.warning(f"โš ๏ธ Could not load cooldown state {path}: {e}") return default_cooldown_state() -def save_cooldown_state(state, path=COOLDOWN_STATE_PATH): +def save_cooldown_state(state, path): try: state["updated_at"] = arrow.utcnow().isoformat() temp_path = f"{path}.tmp" @@ -100,48 +100,48 @@ def save_cooldown_state(state, path=COOLDOWN_STATE_PATH): os.replace(temp_path, path) except Exception as e: - logging.warning(f"Could not save cooldown state {path}: {e}") + logging.warning(f"โš ๏ธ Could not save cooldown state {path}: {e}") -def get_global_post_cooldown_until(): - state = load_cooldown_state() +def get_global_post_cooldown_until(cooldown_path): + state = load_cooldown_state(cooldown_path) return int(state.get("post_creation_cooldown_until", 0) or 0) -def get_global_thumb_cooldown_until(): - state = load_cooldown_state() +def get_global_thumb_cooldown_until(cooldown_path): + state = load_cooldown_state(cooldown_path) return int(state.get("thumb_upload_cooldown_until", 0) or 0) -def is_global_post_cooldown_active(): - return int(time.time()) < get_global_post_cooldown_until() +def is_global_post_cooldown_active(cooldown_path): + return int(time.time()) < get_global_post_cooldown_until(cooldown_path) -def is_global_thumb_cooldown_active(): - return int(time.time()) < get_global_thumb_cooldown_until() +def is_global_thumb_cooldown_active(cooldown_path): + return int(time.time()) < get_global_thumb_cooldown_until(cooldown_path) -def set_global_post_cooldown_until(reset_ts): - state = load_cooldown_state() +def set_global_post_cooldown_until(reset_ts, cooldown_path): + state = load_cooldown_state(cooldown_path) current = int(state.get("post_creation_cooldown_until", 0) or 0) if reset_ts > current: state["post_creation_cooldown_until"] = int(reset_ts) - save_cooldown_state(state) + save_cooldown_state(state, cooldown_path) - final_ts = int(state.get("post_creation_cooldown_until", 0) or 0) + final_ts = int(load_cooldown_state(cooldown_path).get("post_creation_cooldown_until", 0) or 0) return final_ts -def set_global_thumb_cooldown_until(reset_ts): - state = load_cooldown_state() +def set_global_thumb_cooldown_until(reset_ts, cooldown_path): + state = load_cooldown_state(cooldown_path) current = int(state.get("thumb_upload_cooldown_until", 0) or 0) if reset_ts > current: state["thumb_upload_cooldown_until"] = int(reset_ts) - save_cooldown_state(state) + save_cooldown_state(state, cooldown_path) - final_ts = int(state.get("thumb_upload_cooldown_until", 0) or 0) + final_ts = int(load_cooldown_state(cooldown_path).get("thumb_upload_cooldown_until", 0) or 0) return final_ts @@ -157,7 +157,7 @@ def desescapar_unicode(text): try: return html.unescape(text) except Exception as e: - logging.warning(f"Error unescaping unicode/html entities: {e}") + logging.warning(f"โš ๏ธ Error unescaping unicode/html entities: {e}") return text @@ -206,21 +206,10 @@ def process_title(title): title_text = clean_whitespace(title_text) return title_text except Exception as e: - logging.warning(f"Error processing title: {e}") + logging.warning(f"โš ๏ธ Error processing title: {e}") return title or "" -def truncate_text_safely(text, max_length=BSKY_TEXT_MAX_LENGTH): - if len(text) <= max_length: - return text - - truncated = text[:max_length - 3] - last_space = truncated.rfind(" ") - if last_space > 0: - return truncated[:last_space] + "..." - return truncated + "..." - - def build_post_text_variants(title_text, link): title_text = clean_whitespace(title_text) link = canonicalize_url(link) or link or "" @@ -288,9 +277,9 @@ def default_state(): } -def load_state(state_path=STATE_PATH): +def load_state(state_path): if not os.path.exists(state_path): - logging.info(f"No state file found at {state_path}. Starting with empty state.") + logging.info(f"๐Ÿง  No state file found at {state_path}. Starting with empty state.") return default_state() try: @@ -298,7 +287,7 @@ def load_state(state_path=STATE_PATH): state = json.load(f) if not isinstance(state, dict): - logging.warning("State file invalid. Reinitializing.") + logging.warning("โš ๏ธ State file invalid. Reinitializing.") return default_state() state.setdefault("version", 1) @@ -308,11 +297,11 @@ def load_state(state_path=STATE_PATH): return state except Exception as e: - logging.warning(f"Could not load state file {state_path}: {e}. Reinitializing.") + logging.warning(f"โš ๏ธ Could not load state file {state_path}: {e}. Reinitializing.") return default_state() -def save_state(state, state_path=STATE_PATH): +def save_state(state, state_path): try: state["updated_at"] = arrow.utcnow().isoformat() temp_path = f"{state_path}.tmp" @@ -321,10 +310,10 @@ def save_state(state, state_path=STATE_PATH): json.dump(state, f, ensure_ascii=False, indent=2, sort_keys=True) os.replace(temp_path, state_path) - logging.info(f"State saved to {state_path}") + logging.info(f"๐Ÿ’พ State saved to {state_path}") except Exception as e: - logging.error(f"Failed to save state file {state_path}: {e}") + logging.error(f"โŒ Failed to save state file {state_path}: {e}") def prune_state(state, max_entries=5000): @@ -459,7 +448,7 @@ def get_recent_bsky_posts(client, handle, limit=DEDUPE_BSKY_LIMIT): logging.debug(f"Skipping one Bluesky feed item during dedupe fetch: {e}") except Exception as e: - logging.warning(f"Could not fetch recent Bluesky posts for duplicate detection: {e}") + logging.warning(f"โš ๏ธ Could not fetch recent Bluesky posts for duplicate detection: {e}") return recent_posts @@ -597,27 +586,27 @@ def is_timeout_error(error_obj): ]) -def activate_post_creation_cooldown_from_error(error_obj): +def activate_post_creation_cooldown_from_error(error_obj, cooldown_path): reset_ts = get_rate_limit_reset_timestamp(error_obj) if not reset_ts: reset_ts = int(time.time()) + DEFAULT_POST_COOLDOWN_SECONDS - final_ts = set_global_post_cooldown_until(reset_ts) + final_ts = set_global_post_cooldown_until(reset_ts, cooldown_path) logging.error( - f"=== BSKY POST STOPPED: RATE LIMITED === Posting disabled until " + f"๐Ÿ›‘ === BSKY POST STOPPED: RATE LIMITED === Posting disabled until " f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(final_ts))}" ) return final_ts -def activate_thumb_upload_cooldown_from_error(error_obj): +def activate_thumb_upload_cooldown_from_error(error_obj, cooldown_path): reset_ts = get_rate_limit_reset_timestamp(error_obj) if not reset_ts: reset_ts = int(time.time()) + DEFAULT_THUMB_COOLDOWN_SECONDS - final_ts = set_global_thumb_cooldown_until(reset_ts) + final_ts = set_global_thumb_cooldown_until(reset_ts, cooldown_path) logging.warning( - f"Thumbnail uploads disabled until " + f"๐Ÿ–ผ๏ธ Thumbnail uploads disabled until " f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(final_ts))}." ) return final_ts @@ -633,7 +622,7 @@ def get_rate_limit_wait_seconds(error_obj, default_delay): return default_delay -def upload_blob_with_retry(client, binary_data, media_label="media", optional=False, cooldown_on_rate_limit=False): +def upload_blob_with_retry(client, binary_data, media_label="media", optional=False, cooldown_on_rate_limit=False, cooldown_path=DEFAULT_COOLDOWN_STATE_PATH): last_exception = None transient_attempts = 0 @@ -647,11 +636,11 @@ def upload_blob_with_retry(client, binary_data, media_label="media", optional=Fa if is_rate_limited_error(e): if cooldown_on_rate_limit: - activate_thumb_upload_cooldown_from_error(e) + activate_thumb_upload_cooldown_from_error(e, cooldown_path) if optional and cooldown_on_rate_limit: logging.warning( - f"Optional blob upload rate-limited for {media_label}. " + f"๐ŸŸก Optional blob upload rate-limited for {media_label}. " f"Skipping remaining retries and omitting optional media." ) return None @@ -664,29 +653,29 @@ def upload_blob_with_retry(client, binary_data, media_label="media", optional=Fa if attempt < BSKY_BLOB_UPLOAD_MAX_RETRIES: logging.warning( - f"Blob upload rate-limited for {media_label}. " + f"โณ Blob upload rate-limited for {media_label}. " f"Retry {attempt}/{BSKY_BLOB_UPLOAD_MAX_RETRIES} after {wait_seconds}s." ) time.sleep(wait_seconds) continue - logging.warning(f"Exhausted blob upload retries for {media_label}: {repr(e)}") + logging.warning(f"โš ๏ธ Exhausted blob upload retries for {media_label}: {repr(e)}") break if is_transient_blob_error(e) and transient_attempts < BSKY_BLOB_TRANSIENT_ERROR_RETRIES: transient_attempts += 1 wait_seconds = BSKY_BLOB_TRANSIENT_ERROR_DELAY * transient_attempts logging.warning( - f"Transient blob upload failure for {media_label}: {repr(e)}. " + f"โณ Transient blob upload failure for {media_label}: {repr(e)}. " f"Retry {transient_attempts}/{BSKY_BLOB_TRANSIENT_ERROR_RETRIES} after {wait_seconds}s." ) time.sleep(wait_seconds) continue - logging.warning(f"Could not upload {media_label}: {repr(e)}") + logging.warning(f"โš ๏ธ Could not upload {media_label}: {repr(e)}") return None - logging.warning(f"Could not upload {media_label}: {repr(last_exception)}") + logging.warning(f"โš ๏ธ Could not upload {media_label}: {repr(last_exception)}") return None @@ -731,26 +720,26 @@ def compress_external_thumb_to_limit(image_bytes, max_bytes=EXTERNAL_THUMB_MAX_B return data except Exception as e: - logging.warning(f"Could not compress external thumbnail: {repr(e)}") + logging.warning(f"โš ๏ธ Could not compress external thumbnail: {repr(e)}") return None -def get_external_thumb_blob_from_url(image_url, client, http_client): - if is_global_thumb_cooldown_active(): - reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_thumb_cooldown_until())) - logging.info(f"Skipping external thumbnail upload due to active cooldown until {reset_str}") +def get_external_thumb_blob_from_url(image_url, client, http_client, cooldown_path): + if is_global_thumb_cooldown_active(cooldown_path): + reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_thumb_cooldown_until(cooldown_path))) + logging.info(f"๐Ÿ–ผ๏ธ Skipping external thumbnail upload due to active cooldown until {reset_str}") return None try: r = http_client.get(image_url, timeout=HTTP_TIMEOUT, follow_redirects=True) if r.status_code != 200: - logging.warning(f"Could not fetch external thumb {image_url}: HTTP {r.status_code}") + logging.warning(f"โš ๏ธ Could not fetch external thumb {image_url}: HTTP {r.status_code}") return None content = r.content if not content: - logging.warning(f"Could not fetch external thumb {image_url}: empty body") + logging.warning(f"โš ๏ธ Could not fetch external thumb {image_url}: empty body") return None upload_bytes = content @@ -759,7 +748,7 @@ def get_external_thumb_blob_from_url(image_url, client, http_client): if compressed: upload_bytes = compressed else: - logging.warning("Could not compress external thumb to fit limit. Omitting thumbnail.") + logging.warning("โš ๏ธ Could not compress external thumb to fit limit. Omitting thumbnail.") return None return upload_blob_with_retry( @@ -767,11 +756,12 @@ def get_external_thumb_blob_from_url(image_url, client, http_client): upload_bytes, media_label=f"external-thumb:{image_url}", optional=True, - cooldown_on_rate_limit=True + cooldown_on_rate_limit=True, + cooldown_path=cooldown_path ) except Exception as e: - logging.warning(f"Could not fetch/upload external thumb {image_url}: {repr(e)}") + logging.warning(f"โš ๏ธ Could not fetch/upload external thumb {image_url}: {repr(e)}") return None @@ -798,20 +788,20 @@ def fetch_link_metadata(url, http_client): "image": image["content"] if image and image.has_attr("content") else None, } except Exception as e: - logging.warning(f"Could not fetch link metadata for {url}: {e}") + logging.warning(f"โš ๏ธ Could not fetch link metadata for {url}: {e}") return {} -def build_external_link_embed(url, fallback_title, client, http_client): +def build_external_link_embed(url, fallback_title, client, http_client, cooldown_path): link_metadata = fetch_link_metadata(url, http_client) thumb_blob = None if link_metadata.get("image"): - thumb_blob = get_external_thumb_blob_from_url(link_metadata["image"], client, http_client) + thumb_blob = get_external_thumb_blob_from_url(link_metadata["image"], client, http_client, cooldown_path) if thumb_blob: - logging.info("External link card thumbnail prepared successfully") + logging.info("โœ… External link card thumbnail prepared successfully") else: - logging.info("External link card will be posted without thumbnail") + logging.info("โ„น๏ธ External link card will be posted without thumbnail") if link_metadata.get("title") or link_metadata.get("description") or thumb_blob: return models.AppBskyEmbedExternal.Main( @@ -854,7 +844,7 @@ def build_candidates_from_feed(feed): published_at = parse_entry_time(item) if not title_text and not link: - logging.info("Skipping feed item with no usable title and no link.") + logging.info("โญ๏ธ Skipping feed item with no usable title and no link.") continue normalized_title = normalize_text(title_text) @@ -872,7 +862,7 @@ def build_candidates_from_feed(feed): }) except Exception as e: - logging.warning(f"Failed to prepare feed entry candidate: {e}") + logging.warning(f"โš ๏ธ Failed to prepare feed entry candidate: {e}") candidates.sort(key=lambda c: c["published_arrow"] or arrow.get(0)) return candidates @@ -892,30 +882,30 @@ def is_probable_length_error(exc): return any(signal.lower() in text.lower() for signal in signals) -def try_send_post_with_variants(client, text_variants, embed, post_lang): - if is_global_post_cooldown_active(): - reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until())) +def try_send_post_with_variants(client, text_variants, embed, post_lang, cooldown_path): + if is_global_post_cooldown_active(cooldown_path): + reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until(cooldown_path))) raise RuntimeError(f"Posting skipped because global post cooldown is active until {reset_str}") last_exception = None for idx, variant in enumerate(text_variants, start=1): try: - if is_global_post_cooldown_active(): - reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until())) + if is_global_post_cooldown_active(cooldown_path): + reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until(cooldown_path))) raise RuntimeError(f"Posting skipped because global post cooldown is active until {reset_str}") - logging.info(f"Trying post text variant {idx}/{len(text_variants)} (length={len(variant)})") + logging.info(f"๐Ÿ“ Trying post text variant {idx}/{len(text_variants)} (length={len(variant)})") rich_text = make_rich(variant) result = client.send_post(text=rich_text, embed=embed, langs=[post_lang]) return result, variant except Exception as e: last_exception = e - logging.warning(f"Post variant {idx} failed: {repr(e)}") + logging.warning(f"โš ๏ธ Post variant {idx} failed: {repr(e)}") if is_rate_limited_error(e): - activate_post_creation_cooldown_from_error(e) + activate_post_creation_cooldown_from_error(e, cooldown_path) raise if is_timeout_error(e): @@ -939,14 +929,10 @@ def main(): parser.add_argument("bsky_app_password", help="Bluesky app password") parser.add_argument("--service", default="https://bsky.social", help="Bluesky server URL") parser.add_argument("--lang", default="ca", help="Language code for the post") - parser.add_argument("--state-path", default=STATE_PATH, help="Path to local JSON state file") - parser.add_argument("--cooldown-path", default=COOLDOWN_STATE_PATH, help="Path to shared cooldown JSON state file") + parser.add_argument("--state-path", default=DEFAULT_STATE_PATH, help="Path to local JSON state file") + parser.add_argument("--cooldown-path", default=DEFAULT_COOLDOWN_STATE_PATH, help="Path to shared cooldown JSON state file") args = parser.parse_args() - global STATE_PATH, COOLDOWN_STATE_PATH - STATE_PATH = args.state_path - COOLDOWN_STATE_PATH = args.cooldown_path - feed_url = args.rss_feed bsky_handle = args.bsky_handle bsky_username = args.bsky_username @@ -954,10 +940,11 @@ def main(): service_url = args.service post_lang = args.lang state_path = args.state_path + cooldown_path = args.cooldown_path - if is_global_post_cooldown_active(): - reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until())) - logging.warning(f"=== BSKY POST SKIPPED: GLOBAL COOLDOWN === Active until {reset_str}") + if is_global_post_cooldown_active(cooldown_path): + reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until(cooldown_path))) + logging.warning(f"๐ŸŸก === BSKY POST SKIPPED: GLOBAL COOLDOWN === Active until {reset_str}") return client = Client(base_url=service_url) @@ -965,25 +952,25 @@ def main(): backoff = 60 while True: try: - if is_global_post_cooldown_active(): - reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until())) - logging.warning(f"=== BSKY POST SKIPPED: GLOBAL COOLDOWN === Active until {reset_str}") + if is_global_post_cooldown_active(cooldown_path): + reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until(cooldown_path))) + logging.warning(f"๐ŸŸก === BSKY POST SKIPPED: GLOBAL COOLDOWN === Active until {reset_str}") return - logging.info(f"Attempting login to server: {service_url} with user: {bsky_username}") + 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}") + logging.info(f"โœ… Login successful for user: {bsky_username}") break except Exception: - logging.exception("Login exception") + logging.exception("โŒ Login exception") time.sleep(backoff) backoff = min(backoff + 60, 600) state = load_state(state_path) recent_bsky_posts = get_recent_bsky_posts(client, bsky_handle, limit=DEDUPE_BSKY_LIMIT) - logging.info(f"Loaded {len(recent_bsky_posts)} recent Bluesky posts for duplicate detection.") - logging.info(f"Local state currently tracks {len(state.get('posted_entries', {}))} posted items.") + logging.info(f"๐Ÿง  Loaded {len(recent_bsky_posts)} recent Bluesky posts for duplicate detection.") + logging.info(f"๐Ÿง  Local state currently tracks {len(state.get('posted_entries', {}))} posted items.") response = httpx.get(feed_url, timeout=HTTP_TIMEOUT, follow_redirects=True) response.raise_for_status() @@ -994,58 +981,58 @@ def main(): raise ValueError("Could not detect feed encoding.") feed_content = result.text except ValueError: - logging.warning("Could not detect feed encoding with charset_normalizer. Trying latin-1.") + logging.warning("โš ๏ธ Could not detect feed encoding with charset_normalizer. Trying latin-1.") try: feed_content = response.content.decode("latin-1") except UnicodeDecodeError: - logging.warning("Could not decode with latin-1. Trying utf-8 with ignored errors.") + logging.warning("โš ๏ธ Could not decode with latin-1. Trying utf-8 with ignored errors.") feed_content = response.content.decode("utf-8", errors="ignore") feed = fastfeedparser.parse(feed_content) candidates = build_candidates_from_feed(feed) - logging.info(f"Prepared {len(candidates)} feed entry candidates for duplicate comparison.") + logging.info(f"๐Ÿ“ฐ Prepared {len(candidates)} feed entry candidates for duplicate comparison.") entries_to_post = [] for candidate in candidates: is_dup_state, reason_state = candidate_matches_state(candidate, state) if is_dup_state: - logging.info(f"Skipping candidate due to local state duplicate match on: {reason_state}") + logging.info(f"โญ๏ธ Skipping candidate due to local state duplicate match on: {reason_state}") continue is_dup_bsky, reason_bsky = candidate_matches_existing_bsky(candidate, recent_bsky_posts) if is_dup_bsky: - logging.info(f"Skipping candidate due to recent Bluesky duplicate match on: {reason_bsky}") + logging.info(f"โญ๏ธ Skipping candidate due to recent Bluesky duplicate match on: {reason_bsky}") continue entries_to_post.append(candidate) - logging.info(f"{len(entries_to_post)} entries remain after duplicate filtering.") + logging.info(f"๐Ÿ“ฌ {len(entries_to_post)} entries remain after duplicate filtering.") if not entries_to_post: logging.info("โ„น๏ธ Execution finished: no new entries to publish.") return - if is_global_post_cooldown_active(): - reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until())) - logging.warning(f"=== BSKY POST SKIPPED: GLOBAL COOLDOWN === Active until {reset_str}") + if is_global_post_cooldown_active(cooldown_path): + reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until(cooldown_path))) + logging.warning(f"๐ŸŸก === BSKY POST SKIPPED: GLOBAL COOLDOWN === Active until {reset_str}") return noves_entrades = 0 with httpx.Client() as http_client: for candidate in entries_to_post: - if is_global_post_cooldown_active(): - reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until())) - logging.error(f"=== BSKY POST STOPPED: GLOBAL COOLDOWN === Skipping remaining entries until {reset_str}") + if is_global_post_cooldown_active(cooldown_path): + reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until(cooldown_path))) + logging.error(f"๐Ÿ›‘ === BSKY POST STOPPED: GLOBAL COOLDOWN === Skipping remaining entries until {reset_str}") break title_text = candidate["title_text"] canonical_link = candidate["canonical_link"] text_variants = candidate["post_text_variants"] - logging.info(f"Preparing to post RSS entry: {canonical_link or title_text}") - logging.info(f"=== BSKY POST START === {canonical_link or title_text}") + logging.info(f"๐Ÿ“ฐ Preparing to post RSS entry: {canonical_link or title_text}") + logging.info(f"๐Ÿš€ === BSKY POST START === {canonical_link or title_text}") embed = None if canonical_link: @@ -1053,7 +1040,8 @@ def main(): canonical_link, fallback_title=title_text or "Enllaรง", client=client, - http_client=http_client + http_client=http_client, + cooldown_path=cooldown_path ) try: @@ -1061,7 +1049,8 @@ def main(): client=client, text_variants=text_variants, embed=embed, - post_lang=post_lang + post_lang=post_lang, + cooldown_path=cooldown_path ) bsky_uri = getattr(post_result, "uri", None) @@ -1080,28 +1069,28 @@ def main(): recent_bsky_posts = recent_bsky_posts[:DEDUPE_BSKY_LIMIT] noves_entrades += 1 - logging.info(f"=== BSKY POST SUCCESS === {canonical_link or title_text}") - logging.info(f"Posted RSS entry to Bluesky: {canonical_link or title_text}") + logging.info(f"โœ… === BSKY POST SUCCESS === {canonical_link or title_text}") + logging.info(f"๐ŸŽ‰ Posted RSS entry to Bluesky: {canonical_link or title_text}") time.sleep(POST_RETRY_DELAY_SECONDS) except Exception as e: if is_rate_limited_error(e): - reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until())) - logging.error(f"=== BSKY POST FAILED === {canonical_link or title_text}") - logging.error(f"=== BSKY POST STOPPED: RATE LIMITED === Ending publish loop until {reset_str}") + reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until(cooldown_path))) + logging.error(f"โŒ === BSKY POST FAILED === {canonical_link or title_text}") + logging.error(f"๐Ÿ›‘ === BSKY POST STOPPED: RATE LIMITED === Ending publish loop until {reset_str}") break if "global post cooldown is active" in str(e).lower(): - reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until())) - logging.warning(f"=== BSKY POST SKIPPED: GLOBAL COOLDOWN === {canonical_link or title_text}") - logging.warning(f"=== BSKY POST STOPPED: GLOBAL COOLDOWN === Ending publish loop until {reset_str}") + reset_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(get_global_post_cooldown_until(cooldown_path))) + logging.warning(f"๐ŸŸก === BSKY POST SKIPPED: GLOBAL COOLDOWN === {canonical_link or title_text}") + logging.warning(f"๐Ÿ›‘ === BSKY POST STOPPED: GLOBAL COOLDOWN === Ending publish loop until {reset_str}") break if is_timeout_error(e): - logging.error(f"=== BSKY POST FAILED === {canonical_link or title_text} :: timeout") + logging.error(f"โฐ === BSKY POST FAILED === {canonical_link or title_text} :: timeout") break - logging.exception(f"=== BSKY POST FAILED === {canonical_link or title_text}") + logging.exception(f"โŒ === BSKY POST FAILED === {canonical_link or title_text}") if noves_entrades > 0: logging.info(f"๐ŸŽ‰ Execution finished: published {noves_entrades} new entries to Bluesky.")