Spaces:
Running
Running
| # --- START OF MODIFIED FILE app.py --- | |
| import gradio as gr | |
| from atproto import Client, models | |
| import os | |
| import io | |
| from PIL import Image | |
| # --- 配置您的 Bluesky 账户信息 --- | |
| BSKY_HANDLE = os.environ.get("BSKY_HANDLE") | |
| BSKY_APP_PASSWORD = os.environ.get("BSKY_APP_PASSWORD") | |
| # --- 新增:配置缓存目录 --- | |
| IMAGE_CACHE_DIR = "image_cache" | |
| # 确保缓存目录存在 | |
| os.makedirs(IMAGE_CACHE_DIR, exist_ok=True) | |
| client = Client() | |
| # --- 全局登录 --- | |
| try: | |
| print("正在尝试登录 Bluesky...") | |
| if BSKY_HANDLE and BSKY_APP_PASSWORD: | |
| client.login(BSKY_HANDLE, BSKY_APP_PASSWORD) | |
| print(f"Bluesky 登录成功!用户: {client.me.handle}") | |
| else: | |
| print("未找到 Bluesky 环境变量,跳过全局登录。") | |
| except Exception as e: | |
| print(f"Bluesky 全局登录失败: {e}") | |
| pass | |
| def fetch_bsky_gallery(max_posts=51): | |
| if not client.me: | |
| print("客户端未登录,正在尝试重新登录...") | |
| if not BSKY_HANDLE or not BSKY_APP_PASSWORD: | |
| raise gr.Error("Bluesky 登录失败:请在 Hugging Face Spaces 的 Repository secrets 中设置 BSKY_HANDLE 和 BSKY_APP_PASSWORD。") | |
| try: | |
| client.login(BSKY_HANDLE, BSKY_APP_PASSWORD) | |
| print(f"Bluesky 重新登录成功!用户: {client.me.handle}") | |
| except Exception as e: | |
| raise gr.Error(f"Bluesky 登录失败,请检查 Handle 或应用密码并重启程序。错误: {e}") | |
| print(f"正在为用户 {client.me.handle} 获取帖子...") | |
| try: | |
| actor_did = client.me.did | |
| profile_feed = client.get_author_feed(actor=actor_did, limit=max_posts) | |
| print(f"从 API 获取了 {len(profile_feed.feed)} 条帖子,正在开始检查...") | |
| gallery_items = [] | |
| for i, feed_view in enumerate(profile_feed.feed): | |
| post_record = feed_view.post.record | |
| # 核心判断逻辑 | |
| if post_record.embed and isinstance(post_record.embed, models.AppBskyEmbedImages.Main): | |
| print(f"成功匹配到图片帖子 #{i+1}!正在处理...") | |
| images = post_record.embed.images | |
| post_text = getattr(post_record, 'text', '') | |
| for j, image_data in enumerate(images): | |
| try: | |
| image_cid = image_data.image.ref.link | |
| # --- 缓存逻辑开始 --- | |
| # 1. 定义缓存文件的路径 | |
| cached_image_path = os.path.join(IMAGE_CACHE_DIR, f"{image_cid}.jpg") | |
| # 2. 检查缓存是否存在 | |
| if os.path.exists(cached_image_path): | |
| print(f" > 缓存命中!直接使用本地图片: {cached_image_path}") | |
| else: | |
| # 3. 如果缓存不存在,则下载并保存 | |
| print(f" > 缓存未命中。正在下载 CID: {image_cid}...") | |
| params = models.ComAtprotoSyncGetBlob.Params(did=actor_did, cid=image_cid) | |
| image_bytes = client.com.atproto.sync.get_blob(params) | |
| if image_bytes: | |
| # 使用 PIL 打开图片并转换为 RGB | |
| img = Image.open(io.BytesIO(image_bytes)) | |
| if img.mode == 'RGBA': | |
| img = img.convert('RGB') | |
| # 保存到缓存目录 | |
| img.save(cached_image_path, format='JPEG') | |
| print(f" > 图片已成功下载并缓存至: {cached_image_path}") | |
| else: | |
| print(f" > 下载失败,未获取到图片数据。") | |
| continue # 跳过这张图片 | |
| # --- 缓存逻辑结束 --- | |
| # 将缓存路径(或已存在的本地路径)添加到画廊列表 | |
| caption = post_text if j == 0 else "" | |
| gallery_items.append((cached_image_path, caption)) | |
| except Exception as img_e: | |
| print(f"处理单张图片时发生错误: {img_e}") | |
| continue | |
| else: | |
| print(f"帖子 #{i+1} 不包含图片,已跳过。") | |
| print(f"\n检查完成。成功获取并处理了 {len(gallery_items)} 张图片。") | |
| if not gallery_items: | |
| placeholder_image = "https://i.imgur.com/nA8n922.png" | |
| return [(placeholder_image, f"在最近的 {max_posts} 条帖子中没有找到符合条件的图片。")] | |
| return gallery_items | |
| except Exception as e: | |
| print(f"获取帖子时发生错误: {e}") | |
| raise gr.Error(f"获取 Bluesky 帖子时出错: {e}") | |
| with gr.Blocks() as demo: | |
| gr.Markdown(""" | |
| ### 🎨 AiFeiFei 的 Bluesky 数字艺术画廊 | |
| """) | |
| with gr.Row(): | |
| gallery = gr.Gallery( | |
| label="作品集", | |
| columns=3, | |
| height=600, | |
| show_download_button=True, | |
| object_fit="contain", | |
| show_label=False | |
| ) | |
| with gr.Row(): | |
| refresh_button = gr.Button("🔄 刷新画廊") | |
| demo.load(fn=fetch_bsky_gallery, inputs=None, outputs=gallery) | |
| refresh_button.click(fn=fetch_bsky_gallery, inputs=None, outputs=gallery) | |
| if __name__ == "__main__": | |
| demo.launch(debug=True) | |
| # --- END OF MODIFIED FILE app.py --- |