Nyanpre commited on
Commit
310f1f2
·
verified ·
1 Parent(s): 40a70e7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +40 -33
app.py CHANGED
@@ -17,25 +17,30 @@ def analyze_and_output(my_id, my_pw, target_id, progress=gr.Progress()):
17
 
18
  posts_data = []
19
  hashtags = []
20
- reply_users = [] # メンション・リプライ
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 feed_view.reason and hasattr(feed_view.reason, 'by'):
34
- # 自分がリポストした投稿の、元の投稿者
35
- orig_author = feed_view.post.author.handle
36
- if orig_author != target_handle:
37
- repost_users.append(orig_author)
38
- continue # リポスト自体は本文解析から除外
 
39
 
40
  post = feed_view.post
41
  if post.author.handle == target_handle:
@@ -43,46 +48,52 @@ def analyze_and_output(my_id, my_pw, target_id, progress=gr.Progress()):
43
  post_url = f"https://bsky.app/profile/{target_handle}/post/{rkey}"
44
 
45
  posts_data.append({
46
- 'text': post.record.text,
47
  'created_at': pd.to_datetime(post.record.created_at),
48
  'likes': post.like_count,
49
  'reposts': post.repost_count,
50
- 'score': post.like_count + post.repost_count,
51
  'url': post_url
52
  })
53
 
54
- hashtags.extend(re.findall(r'#(\w+)', post.record.text))
 
55
 
56
- # リプライ・メンション相手
57
  if feed_view.reply and feed_view.reply.parent:
58
- if hasattr(feed_view.reply.parent, 'author'):
59
- parent_author = feed_view.reply.parent.author.handle
 
60
  if parent_author != target_handle:
61
  reply_users.append(parent_author)
62
 
63
  cursor = response.cursor
64
  if not cursor or len(posts_data) >= max_limit: break
65
- progress(len(posts_data)/max_limit * 0.5, desc=f"フィード解析中...")
66
 
67
  # --- 2. いいね一覧の分析 ---
68
  progress(0.6, desc="いいねした相手を分析中...")
69
  like_cursor = None
70
- # いいねは直近200件程度をサンプルとして取得(API負荷考慮)
71
  for _ in range(2):
72
- likes_resp = client.get_actor_likes(actor=profile.did, limit=100, cursor=like_cursor)
73
- for like_item in likes_resp.feed:
74
- if like_item.post and like_item.post.author:
75
- l_author = like_item.post.author.handle
76
- if l_author != target_handle:
77
- like_users.append(l_author)
78
- like_cursor = likes_resp.cursor
79
- if not like_cursor: break
 
 
 
 
80
 
81
- if not posts_data: return "データし", "", "", "終了"
82
 
83
  # --- 3. HTML作成 ---
84
  df = pd.DataFrame(posts_data)
85
- first_post = df.iloc[-1]
 
86
  days_active = max((datetime.now().replace(tzinfo=None) - first_post['created_at'].replace(tzinfo=None)).days, 1)
87
 
88
  stats_html = f"""
@@ -99,7 +110,6 @@ def analyze_and_output(my_id, my_pw, target_id, progress=gr.Progress()):
99
  </div>
100
  """
101
 
102
- # ランキングHTML
103
  def make_rank_list(title, icon, counter_list):
104
  html = f"<b>{icon} {title}</b><div style='font-size: 0.85em; color: #444; margin: 5px 0 15px 0;'>"
105
  if not counter_list: return html + "データなし</div>"
@@ -118,15 +128,12 @@ def analyze_and_output(my_id, my_pw, target_id, progress=gr.Progress()):
118
  </div>
119
  """
120
 
121
- # ベストポスト等 (前回同様)
122
  top_3 = df.sort_values('score', ascending=False).head(3)
123
- posts_html = f"<h4>🌱 初投稿 ({first_post['created_at'].strftime('%Y/%m/%d')})</h4><p style='font-size: 0.9em; background:#f9f9f9; padding:10px; border-radius:5px;'>{first_post['text']}</p><h4 style='color: #ff4081;'>🏆 ベストポスト Top 3</h4>"
124
  for i, (_, row) in enumerate(top_3.iterrows(), 1):
125
  posts_html += f"<div style='border: 1px solid #ffc107; padding: 10px; margin-bottom: 8px; border-radius: 8px; font-size: 0.9em;'><b>第{i}位</b> (❤️{row['likes']} 🔄{row['reposts']})<br>{row['text'][:80]}...<br><a href='{row['url']}' target='_blank' style='color:#ff4081;'>投稿を見る</a></div>"
126
 
127
  return stats_html, rank_html, posts_html, "解析完了!"
128
 
129
  except Exception as e:
130
- return f"エラー: {str(e)}", "", "", "失敗"
131
-
132
- # (Gradio UI部分は変更なし)
 
17
 
18
  posts_data = []
19
  hashtags = []
20
+ reply_users = []
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
+ # 安全策1: 投稿自体が存在するか
33
+ if not feed_view.post or not hasattr(feed_view.post, 'author'):
34
+ continue
35
+
36
  # リポストの分析
37
  if feed_view.reason and hasattr(feed_view.reason, 'by'):
38
+ # 安全策2: リポスト元の投稿者が存在するか
39
+ if hasattr(feed_view.post, 'author') and feed_view.post.author:
40
+ orig_author = feed_view.post.author.handle
41
+ if orig_author != target_handle:
42
+ repost_users.append(orig_author)
43
+ continue
44
 
45
  post = feed_view.post
46
  if post.author.handle == target_handle:
 
48
  post_url = f"https://bsky.app/profile/{target_handle}/post/{rkey}"
49
 
50
  posts_data.append({
51
+ 'text': post.record.text if hasattr(post.record, 'text') else "",
52
  'created_at': pd.to_datetime(post.record.created_at),
53
  'likes': post.like_count,
54
  'reposts': post.repost_count,
55
+ 'score': (post.like_count or 0) + (post.repost_count or 0),
56
  'url': post_url
57
  })
58
 
59
+ if hasattr(post.record, 'text'):
60
+ hashtags.extend(re.findall(r'#(\w+)', post.record.text))
61
 
62
+ # 安全策3: リプライ相手の存在チェック
63
  if feed_view.reply and feed_view.reply.parent:
64
+ parent = feed_view.reply.parent
65
+ if hasattr(parent, 'author') and parent.author:
66
+ parent_author = parent.author.handle
67
  if parent_author != target_handle:
68
  reply_users.append(parent_author)
69
 
70
  cursor = response.cursor
71
  if not cursor or len(posts_data) >= max_limit: break
72
+ progress(min(len(posts_data)/max_limit * 0.5, 0.5), desc=f"{len(posts_data)}件取得中...")
73
 
74
  # --- 2. いいね一覧の分析 ---
75
  progress(0.6, desc="いいねした相手を分析中...")
76
  like_cursor = None
 
77
  for _ in range(2):
78
+ try:
79
+ likes_resp = client.get_actor_likes(actor=profile.did, limit=100, cursor=like_cursor)
80
+ for like_item in likes_resp.feed:
81
+ # 安全策4: いいね先の投稿者が存在するか
82
+ if like_item.post and hasattr(like_item.post, 'author') and like_item.post.author:
83
+ l_author = like_item.post.author.handle
84
+ if l_author != target_handle:
85
+ like_users.append(l_author)
86
+ like_cursor = likes_resp.cursor
87
+ if not like_cursor: break
88
+ except:
89
+ break # いいね取得でエラーが出ても止まらないようにする
90
 
91
+ if not posts_data: return "有効な投稿データが見つかりませんで", "", "", "終了"
92
 
93
  # --- 3. HTML作成 ---
94
  df = pd.DataFrame(posts_data)
95
+ df = df.sort_values('created_at', ascending=True) # 古い順にして初投稿を確定
96
+ first_post = df.iloc[0]
97
  days_active = max((datetime.now().replace(tzinfo=None) - first_post['created_at'].replace(tzinfo=None)).days, 1)
98
 
99
  stats_html = f"""
 
110
  </div>
111
  """
112
 
 
113
  def make_rank_list(title, icon, counter_list):
114
  html = f"<b>{icon} {title}</b><div style='font-size: 0.85em; color: #444; margin: 5px 0 15px 0;'>"
115
  if not counter_list: return html + "データなし</div>"
 
128
  </div>
129
  """
130
 
 
131
  top_3 = df.sort_values('score', ascending=False).head(3)
132
+ posts_html = f"<h4>🌱 取得範囲内の初投稿 ({first_post['created_at'].strftime('%Y/%m/%d')})</h4><p style='font-size: 0.9em; background:#f9f9f9; padding:10px; border-radius:5px;'>{first_post['text']}</p><h4 style='color: #ff4081;'>🏆 ベストポスト Top 3</h4>"
133
  for i, (_, row) in enumerate(top_3.iterrows(), 1):
134
  posts_html += f"<div style='border: 1px solid #ffc107; padding: 10px; margin-bottom: 8px; border-radius: 8px; font-size: 0.9em;'><b>第{i}位</b> (❤️{row['likes']} 🔄{row['reposts']})<br>{row['text'][:80]}...<br><a href='{row['url']}' target='_blank' style='color:#ff4081;'>投稿を見る</a></div>"
135
 
136
  return stats_html, rank_html, posts_html, "解析完了!"
137
 
138
  except Exception as e:
139
+ return f"エラー詳細: {str(e)}", "", "", "失敗"