Nyanpre commited on
Commit
aa4ebb0
·
verified ·
1 Parent(s): 847dbda

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -56
app.py CHANGED
@@ -8,6 +8,7 @@ from collections import Counter
8
 
9
  def analyze_and_output(my_id, my_pw, target_id, progress=gr.Progress()):
10
  try:
 
11
  client = Client()
12
  my_id = my_id.replace('@', '').strip()
13
  target_handle = target_id.replace('@', '').strip()
@@ -21,35 +22,39 @@ def analyze_and_output(my_id, my_pw, target_id, progress=gr.Progress()):
21
  repost_users = []
22
  like_users = []
23
 
24
- max_limit = 1000
25
 
26
- # --- 1. 投稿フィードの分析 ---
27
  progress(0, desc="フィードを取得中...")
28
  cursor = None
29
- while len(posts_data) < max_limit:
30
- response = client.get_author_feed(actor=profile.did, limit=100, cursor=cursor)
 
 
 
 
 
 
 
 
31
  for feed_view in response.feed:
32
- # 投稿と著者の存在チェック
33
- if not feed_view.post or not hasattr(feed_view.post, 'author') or not feed_view.post.author:
34
- continue
35
-
36
- # リポストの分析
37
- if feed_view.reason and hasattr(feed_view.reason, 'by'):
38
- orig_author = feed_view.post.author.handle
39
- if orig_author != target_handle:
40
- repost_users.append(orig_author)
41
  continue
42
 
43
- post = feed_view.post
44
- if post.author.handle == target_handle:
45
- # recordの存在チェック
46
- if not hasattr(post, 'record') or post.record is None:
47
- continue
48
-
49
  rkey = post.uri.split('/')[-1]
50
  post_url = f"https://bsky.app/profile/{target_handle}/post/{rkey}"
51
 
52
- # 各種カウント(None対策)
53
  likes = getattr(post, 'like_count', 0) or 0
54
  reposts = getattr(post, 'repost_count', 0) or 0
55
  created_at_raw = getattr(post.record, 'created_at', None)
@@ -64,49 +69,43 @@ def analyze_and_output(my_id, my_pw, target_id, progress=gr.Progress()):
64
  'score': likes + reposts,
65
  'url': post_url
66
  })
67
-
68
  if text:
69
  hashtags.extend(re.findall(r'#(\w+)', text))
70
 
71
  # リプライ相手
72
- if feed_view.reply and feed_view.reply.parent:
73
  parent = feed_view.reply.parent
74
  if hasattr(parent, 'author') and parent.author:
75
  p_handle = parent.author.handle
76
  if p_handle != target_handle:
77
  reply_users.append(p_handle)
78
 
79
- cursor = response.cursor
80
  if not cursor or len(posts_data) >= max_limit: break
81
  progress(min(len(posts_data)/max_limit * 0.5, 0.5), desc=f"{len(posts_data)}件取得中...")
82
 
83
- # --- 2. いいね一覧の分析 ---
84
  progress(0.6, desc="いいねを分析中...")
85
- like_cursor = None
86
  try:
87
- for _ in range(2):
88
- likes_resp = client.get_actor_likes(actor=profile.did, limit=100, cursor=like_cursor)
89
  for like_item in likes_resp.feed:
90
  if like_item.post and hasattr(like_item.post, 'author') and like_item.post.author:
91
  l_handle = like_item.post.author.handle
92
  if l_handle != target_handle:
93
  like_users.append(l_handle)
94
- like_cursor = likes_resp.cursor
95
- if not like_cursor: break
96
- except Exception:
97
- pass # いいねが取得できなくても続行
98
 
99
- if not posts_data:
100
- return "有効な投稿データが1件も見つかりませんでした。", "", "", "完了(データなし)"
 
101
 
102
- # --- 3. 解析とHTML作成 ---
103
  df = pd.DataFrame(posts_data)
104
  df = df.sort_values('created_at', ascending=True)
105
- first_post = df.iloc[0]
106
 
107
- # 継続日数の計算
108
- delta = datetime.now().replace(tzinfo=None) - first_post['created_at'].replace(tzinfo=None)
109
- days_active = max(delta.days, 1)
110
 
111
  stats_html = f"""
112
  <div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 20px;'>
@@ -141,29 +140,42 @@ def analyze_and_output(my_id, my_pw, target_id, progress=gr.Progress()):
141
  </div>
142
  """
143
 
144
- # ベストポスト Top 3
145
  top_3 = df.sort_values('score', ascending=False).head(3)
146
- posts_html = f"""
147
- <div style='margin-bottom: 20px; padding: 12px; border-radius: 8px; background: #fff8e1; border: 1px solid #ffe082;'>
148
- <small style='color: #ffa000; font-weight: bold;'>🌱 取得範囲内の初投稿 ({first_post['created_at'].strftime('%Y/%m/%d')})</small>
149
- <p style='font-size: 0.9em; margin: 5px 0;'>{first_post['text']}</p>
150
- <a href='{first_post['url']}' target='_blank' style='font-size: 0.8em; color: #ff8f00;'>投稿をみる</a>
151
- </div>
152
- <h4 style='color: #d81b60; margin-bottom: 10px;'>🏆 ベストポスト Top 3</h4>
153
- """
154
  for i, (_, row) in enumerate(top_3.iterrows(), 1):
155
  posts_html += f"""
156
- <div style='border: 1px solid #f0f0f0; padding: 10px; margin-bottom: 10px; border-radius: 8px; background: white;'>
157
- <div style='display: flex; justify-content: space-between;'>
158
- <b style='color: #ffb300;'>{i}</b>
159
- <span style='font-size: 0.8em; color: #999;'>❤️ {row['likes']} 🔄 {row['reposts']}</span>
160
- </div>
161
- <p style='font-size: 0.9em; margin: 8px 0;'>{row['text'][:100]}...</p>
162
- <a href='{row['url']}' target='_blank' style='color: #d81b60; font-size: 0.85em;'>投稿をみる</a>
163
  </div>
164
  """
165
 
166
- return stats_html, rank_html, posts_html, "すべての解析が完了しました!"
167
 
168
  except Exception as e:
169
- return f"実行エラー: {str(e)}", "", "", "解析に失敗しました"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  def analyze_and_output(my_id, my_pw, target_id, progress=gr.Progress()):
10
  try:
11
+ # --- 1. ログイン ---
12
  client = Client()
13
  my_id = my_id.replace('@', '').strip()
14
  target_handle = target_id.replace('@', '').strip()
 
22
  repost_users = []
23
  like_users = []
24
 
25
+ max_limit = 500
26
 
27
+ # --- 2. 投稿フィードの分析 ---
28
  progress(0, desc="フィードを取得中...")
29
  cursor = None
30
+
31
+ for _ in range(10):
32
+ try:
33
+ response = client.get_author_feed(actor=profile.did, limit=100, cursor=cursor)
34
+ except Exception:
35
+ break
36
+
37
+ if not response or not hasattr(response, 'feed') or not response.feed:
38
+ break
39
+
40
  for feed_view in response.feed:
41
+ if not getattr(feed_view, 'post', None): continue
42
+ post = feed_view.post
43
+
44
+ # リポスト分析
45
+ if feed_view.reason:
46
+ if hasattr(post, 'author') and post.author:
47
+ if post.author.handle != target_handle:
48
+ repost_users.append(post.author.handle)
 
49
  continue
50
 
51
+ # 本人投稿分析
52
+ if hasattr(post, 'author') and post.author and post.author.handle == target_handle:
53
+ if not hasattr(post, 'record'): continue
54
+
 
 
55
  rkey = post.uri.split('/')[-1]
56
  post_url = f"https://bsky.app/profile/{target_handle}/post/{rkey}"
57
 
 
58
  likes = getattr(post, 'like_count', 0) or 0
59
  reposts = getattr(post, 'repost_count', 0) or 0
60
  created_at_raw = getattr(post.record, 'created_at', None)
 
69
  'score': likes + reposts,
70
  'url': post_url
71
  })
 
72
  if text:
73
  hashtags.extend(re.findall(r'#(\w+)', text))
74
 
75
  # リプライ相手
76
+ if getattr(feed_view, 'reply', None) and feed_view.reply.parent:
77
  parent = feed_view.reply.parent
78
  if hasattr(parent, 'author') and parent.author:
79
  p_handle = parent.author.handle
80
  if p_handle != target_handle:
81
  reply_users.append(p_handle)
82
 
83
+ cursor = getattr(response, 'cursor', None)
84
  if not cursor or len(posts_data) >= max_limit: break
85
  progress(min(len(posts_data)/max_limit * 0.5, 0.5), desc=f"{len(posts_data)}件取得中...")
86
 
87
+ # --- 3. いいね一覧の分析 ---
88
  progress(0.6, desc="いいねを分析中...")
 
89
  try:
90
+ likes_resp = client.get_actor_likes(actor=profile.did, limit=50)
91
+ if likes_resp and hasattr(likes_resp, 'feed'):
92
  for like_item in likes_resp.feed:
93
  if like_item.post and hasattr(like_item.post, 'author') and like_item.post.author:
94
  l_handle = like_item.post.author.handle
95
  if l_handle != target_handle:
96
  like_users.append(l_handle)
97
+ except:
98
+ pass
 
 
99
 
100
+ # --- 4. 解析とHTML作成 ---
101
+ if not posts_data:
102
+ return "有効なデータが見つかりませんでした。", "", "", "完了(データ不足)"
103
 
 
104
  df = pd.DataFrame(posts_data)
105
  df = df.sort_values('created_at', ascending=True)
 
106
 
107
+ first_post_time = df.iloc[0]['created_at'].replace(tzinfo=None)
108
+ days_active = max((datetime.now().replace(tzinfo=None) - first_post_time).days, 1)
 
109
 
110
  stats_html = f"""
111
  <div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 20px;'>
 
140
  </div>
141
  """
142
 
 
143
  top_3 = df.sort_values('score', ascending=False).head(3)
144
+ posts_html = f"<h4>🌱 初投稿 ({df.iloc[0]['created_at'].strftime('%Y/%m/%d')})</h4><p style='font-size: 0.9em; background:#f9f9f9; padding:10px; border-radius:5px;'>{df.iloc[0]['text']}</p><h4>🏆 ベストポスト Top 3</h4>"
145
+
 
 
 
 
 
 
146
  for i, (_, row) in enumerate(top_3.iterrows(), 1):
147
  posts_html += f"""
148
+ <div style='border: 1px solid #eee; padding: 10px; margin-bottom: 8px; border-radius: 8px; background: white;'>
149
+ <b>第{i}位</b> (❤️{row['likes']} 🔄{row['reposts']})<br>
150
+ <p style='margin:5px 0;'>{row['text'][:80]}...</p>
151
+ <a href='{row['url']}' target='_blank' style='color:#d81b60; font-size:0.8em;'>表示</a>
 
 
 
152
  </div>
153
  """
154
 
155
+ return stats_html, rank_html, posts_html, "解析が完了しました!"
156
 
157
  except Exception as e:
158
+ return f"エラーが発生しました: {str(e)}", "", "", "失敗"
159
+
160
+ # --- UI ---
161
+ with gr.Blocks(title="Bluesky Analysis") as demo:
162
+ gr.Markdown("# 🦋 Bluesky 自己分析ダッシュボード")
163
+ with gr.Row():
164
+ with gr.Column(scale=1):
165
+ in_id = gr.Textbox(label="自分のハンドル", placeholder="example.bsky.social")
166
+ in_pw = gr.Textbox(label="アプリパスワード", type="password")
167
+ in_target = gr.Textbox(label="解析したい相手のID")
168
+ btn = gr.Button("分析開始", variant="primary")
169
+ status = gr.Textbox(label="ステータス", interactive=False)
170
+ with gr.Column(scale=2):
171
+ out_stats = gr.HTML()
172
+ out_rank = gr.HTML()
173
+ out_posts = gr.HTML()
174
+
175
+ btn.click(
176
+ fn=analyze_and_output,
177
+ inputs=[in_id, in_pw, in_target],
178
+ outputs=[out_stats, out_rank, out_posts, status]
179
+ )
180
+
181
+ demo.launch()