File size: 5,697 Bytes
a5914e7
 
5141a1d
 
 
 
236b21e
5141a1d
 
 
 
 
a5914e7
 
 
 
 
 
5141a1d
 
 
 
 
236b21e
 
 
 
44590c9
5141a1d
 
 
 
3b7098a
5141a1d
236b21e
 
 
 
 
 
 
 
5141a1d
 
 
 
 
 
 
44590c9
5141a1d
 
44590c9
5141a1d
 
44590c9
5141a1d
a5914e7
5141a1d
 
 
44590c9
5141a1d
b4ae552
a5914e7
 
 
307e610
a5914e7
 
 
 
 
 
 
 
 
 
 
5141a1d
236b21e
 
a5914e7
 
 
 
 
 
 
 
 
 
 
 
5141a1d
 
a5914e7
5141a1d
44590c9
a5914e7
44590c9
5141a1d
44590c9
5141a1d
 
b4ae552
44590c9
5141a1d
 
 
 
 
 
 
9170381
 
a2c46a4
9170381
 
c46a0a4
 
42aed2b
76bc2e4
e48aea9
c46a0a4
 
 
ee59cc5
9170381
5141a1d
 
 
 
 
a5914e7
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# --- 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 ---