Size
14.4 KB
Version
1.1.1
Created
Mar 18, 2026
Updated
3 days ago
1// ==UserScript==
2// @name Threads Post Analytics
3// @description Analyze Threads posts with engagement metrics and statistics
4// @version 1.1.1
5// @match https://*.threads.com/*
6// @icon https://static.cdninstagram.com/rsrc.php/ye/r/lEu8iVizmNW.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Threads Post Analytics 已啟動');
12
13 // 防抖函數
14 function debounce(func, wait) {
15 let timeout;
16 return function executedFunction(...args) {
17 const later = () => {
18 clearTimeout(timeout);
19 func(...args);
20 };
21 clearTimeout(timeout);
22 timeout = setTimeout(later, wait);
23 };
24 }
25
26 // 收集貼文數據
27 function collectPostData() {
28 console.log('開始收集貼文數據...');
29 const posts = [];
30
31 // 查找所有貼文容器
32 const postElements = document.querySelectorAll('div[data-virtualized="false"]');
33 console.log(`找到 ${postElements.length} 個貼文元素`);
34
35 postElements.forEach((postElement, index) => {
36 try {
37 // 獲取貼文文字內容
38 const textElement = postElement.querySelector('span.x1lliihq.x1plvlek.xryxfnj.x1n2onr6.x1ji0vk5.x18bv5gf.x193iq5w.xeuugli.x1fj9vlw.x13faqbe.x1vvkbs.x1s928wv.xhkezso.x1gmr53x.x1cpjm7i.x1fgarty.x1943h6x.x1i0vuye.x1ms8i2q.xo1l8bm.x5n08af.x10wh9bi.x1wdrske.x8viiok.x18hxmgj');
39 const postText = textElement ? textElement.textContent.trim() : '';
40
41 // 獲取作者名稱
42 const authorElement = postElement.querySelector('span.x1lliihq.x1plvlek.xryxfnj.x1n2onr6.x1ji0vk5.x18bv5gf.x193iq5w.xeuugli.x1fj9vlw.x13faqbe.x1vvkbs.x1s928wv.xhkezso.x1gmr53x.x1cpjm7i.x1fgarty.x1943h6x.x1i0vuye.xvs91rp.x1s688f.x5n08af.x10wh9bi.x1wdrske.x8viiok.x18hxmgj');
43 const authorName = authorElement ? authorElement.textContent.trim() : '未知';
44
45 // 獲取互動數據(按讚、留言、分享等)
46 const engagementElements = postElement.querySelectorAll('span.x1o0tod.x10l6tqk.x13vifvy');
47 const engagementCounts = Array.from(engagementElements).map(el => {
48 const text = el.textContent.trim();
49 // 處理 K (千) 和 M (百萬) 單位
50 if (text.includes('K')) {
51 return parseFloat(text.replace('K', '')) * 1000;
52 } else if (text.includes('M')) {
53 return parseFloat(text.replace('M', '')) * 1000000;
54 }
55 return parseInt(text) || 0;
56 });
57
58 const postData = {
59 index: index + 1,
60 author: authorName,
61 text: postText.substring(0, 100) + (postText.length > 100 ? '...' : ''),
62 fullText: postText,
63 likes: engagementCounts[0] || 0,
64 comments: engagementCounts[1] || 0,
65 shares: engagementCounts[2] || 0,
66 totalEngagement: (engagementCounts[0] || 0) + (engagementCounts[1] || 0) + (engagementCounts[2] || 0)
67 };
68
69 posts.push(postData);
70 console.log(`貼文 ${index + 1}:`, postData);
71 } catch (error) {
72 console.error(`處理貼文 ${index + 1} 時發生錯誤:`, error);
73 }
74 });
75
76 return posts;
77 }
78
79 // 分析貼文數據
80 function analyzePostData(posts) {
81 if (posts.length === 0) {
82 return {
83 totalPosts: 0,
84 message: '未找到貼文數據'
85 };
86 }
87
88 const totalLikes = posts.reduce((sum, post) => sum + post.likes, 0);
89 const totalComments = posts.reduce((sum, post) => sum + post.comments, 0);
90 const totalShares = posts.reduce((sum, post) => sum + post.shares, 0);
91 const totalEngagement = posts.reduce((sum, post) => sum + post.totalEngagement, 0);
92
93 const avgLikes = totalLikes / posts.length;
94 const avgComments = totalComments / posts.length;
95 const avgShares = totalShares / posts.length;
96 const avgEngagement = totalEngagement / posts.length;
97
98 // 找出最受歡迎的貼文
99 const topPost = posts.reduce((max, post) =>
100 post.totalEngagement > max.totalEngagement ? post : max
101 , posts[0]);
102
103 return {
104 totalPosts: posts.length,
105 totalLikes,
106 totalComments,
107 totalShares,
108 totalEngagement,
109 avgLikes: avgLikes.toFixed(1),
110 avgComments: avgComments.toFixed(1),
111 avgShares: avgShares.toFixed(1),
112 avgEngagement: avgEngagement.toFixed(1),
113 topPost,
114 posts
115 };
116 }
117
118 // 顯示分析結果
119 function displayAnalysisResults(analysis) {
120 // 移除舊的結果視窗
121 const oldModal = document.getElementById('threads-analytics-modal');
122 if (oldModal) {
123 oldModal.remove();
124 }
125
126 // 創建模態視窗
127 const modal = document.createElement('div');
128 modal.id = 'threads-analytics-modal';
129 modal.style.cssText = `
130 position: fixed;
131 top: 50%;
132 left: 50%;
133 transform: translate(-50%, -50%);
134 background: white;
135 border-radius: 12px;
136 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
137 z-index: 10000;
138 max-width: 600px;
139 max-height: 80vh;
140 overflow-y: auto;
141 padding: 24px;
142 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
143 `;
144
145 const content = `
146 <div style="margin-bottom: 20px;">
147 <h2 style="margin: 0 0 16px 0; font-size: 24px; font-weight: 600; color: #000;">📊 貼文分析結果</h2>
148 <button id="close-analytics-modal" style="position: absolute; top: 16px; right: 16px; background: none; border: none; font-size: 24px; cursor: pointer; color: #666;">×</button>
149 </div>
150
151 <div style="background: #f0f0f0; border-radius: 8px; padding: 16px; margin-bottom: 16px;">
152 <h3 style="margin: 0 0 12px 0; font-size: 16px; font-weight: 600; color: #333;">總覽</h3>
153 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
154 <div>
155 <div style="font-size: 14px; color: #666;">總貼文數</div>
156 <div style="font-size: 24px; font-weight: 600; color: #000;">${analysis.totalPosts}</div>
157 </div>
158 <div>
159 <div style="font-size: 14px; color: #666;">總互動數</div>
160 <div style="font-size: 24px; font-weight: 600; color: #000;">${analysis.totalEngagement.toLocaleString()}</div>
161 </div>
162 </div>
163 </div>
164
165 <div style="background: #f0f0f0; border-radius: 8px; padding: 16px; margin-bottom: 16px;">
166 <h3 style="margin: 0 0 12px 0; font-size: 16px; font-weight: 600; color: #333;">互動統計</h3>
167 <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin-bottom: 12px;">
168 <div>
169 <div style="font-size: 14px; color: #666;">❤️ 總按讚</div>
170 <div style="font-size: 20px; font-weight: 600; color: #e0245e;">${analysis.totalLikes.toLocaleString()}</div>
171 </div>
172 <div>
173 <div style="font-size: 14px; color: #666;">💬 總留言</div>
174 <div style="font-size: 20px; font-weight: 600; color: #1da1f2;">${analysis.totalComments.toLocaleString()}</div>
175 </div>
176 <div>
177 <div style="font-size: 14px; color: #666;">🔄 總分享</div>
178 <div style="font-size: 20px; font-weight: 600; color: #17bf63;">${analysis.totalShares.toLocaleString()}</div>
179 </div>
180 </div>
181 <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px;">
182 <div>
183 <div style="font-size: 14px; color: #666;">平均按讚</div>
184 <div style="font-size: 16px; font-weight: 600; color: #000;">${analysis.avgLikes}</div>
185 </div>
186 <div>
187 <div style="font-size: 14px; color: #666;">平均留言</div>
188 <div style="font-size: 16px; font-weight: 600; color: #000;">${analysis.avgComments}</div>
189 </div>
190 <div>
191 <div style="font-size: 14px; color: #666;">平均分享</div>
192 <div style="font-size: 16px; font-weight: 600; color: #000;">${analysis.avgShares}</div>
193 </div>
194 </div>
195 </div>
196
197 ${analysis.topPost ? `
198 <div style="background: #fff3cd; border-radius: 8px; padding: 16px; margin-bottom: 16px; border: 2px solid #ffc107;">
199 <h3 style="margin: 0 0 12px 0; font-size: 16px; font-weight: 600; color: #333;">🏆 最受歡迎的貼文</h3>
200 <div style="font-size: 14px; color: #666; margin-bottom: 8px;">作者: <strong>${analysis.topPost.author}</strong></div>
201 <div style="font-size: 14px; color: #333; margin-bottom: 8px; line-height: 1.4;">${analysis.topPost.text}</div>
202 <div style="display: flex; gap: 16px; font-size: 14px;">
203 <span>❤️ ${analysis.topPost.likes.toLocaleString()}</span>
204 <span>💬 ${analysis.topPost.comments.toLocaleString()}</span>
205 <span>🔄 ${analysis.topPost.shares.toLocaleString()}</span>
206 <span style="font-weight: 600;">總計: ${analysis.topPost.totalEngagement.toLocaleString()}</span>
207 </div>
208 </div>
209 ` : ''}
210
211 <div style="margin-top: 16px;">
212 <button id="export-analytics-data" style="width: 100%; padding: 12px; background: #0095f6; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer;">
213 匯出數據 (JSON)
214 </button>
215 </div>
216 `;
217
218 modal.innerHTML = content;
219
220 // 創建背景遮罩
221 const overlay = document.createElement('div');
222 overlay.id = 'threads-analytics-overlay';
223 overlay.style.cssText = `
224 position: fixed;
225 top: 0;
226 left: 0;
227 right: 0;
228 bottom: 0;
229 background: rgba(0, 0, 0, 0.5);
230 z-index: 9999;
231 `;
232
233 document.body.appendChild(overlay);
234 document.body.appendChild(modal);
235
236 // 關閉按鈕事件
237 document.getElementById('close-analytics-modal').addEventListener('click', () => {
238 modal.remove();
239 overlay.remove();
240 });
241
242 // 點擊遮罩關閉
243 overlay.addEventListener('click', () => {
244 modal.remove();
245 overlay.remove();
246 });
247
248 // 匯出數據按鈕事件
249 document.getElementById('export-analytics-data').addEventListener('click', () => {
250 const dataStr = JSON.stringify(analysis, null, 2);
251 const dataBlob = new Blob([dataStr], { type: 'application/json' });
252 const url = URL.createObjectURL(dataBlob);
253 const link = document.createElement('a');
254 link.href = url;
255 link.download = `threads-analytics-${new Date().toISOString().split('T')[0]}.json`;
256 link.click();
257 URL.revokeObjectURL(url);
258 });
259
260 console.log('分析結果已顯示');
261 }
262
263 // 執行分析
264 function runAnalysis() {
265 console.log('執行貼文分析...');
266 const posts = collectPostData();
267 const analysis = analyzePostData(posts);
268 displayAnalysisResults(analysis);
269 }
270
271 // 創建分析按鈕
272 function createAnalysisButton() {
273 // 檢查按鈕是否已存在
274 if (document.getElementById('threads-analytics-button')) {
275 console.log('分析按鈕已存在');
276 return;
277 }
278
279 const button = document.createElement('button');
280 button.id = 'threads-analytics-button';
281 button.innerHTML = '📊 分析貼文';
282 button.style.cssText = `
283 position: fixed;
284 bottom: 24px;
285 right: 24px;
286 padding: 12px 24px;
287 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
288 color: white;
289 border: none;
290 border-radius: 24px;
291 font-size: 16px;
292 font-weight: 600;
293 cursor: pointer;
294 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
295 z-index: 9998;
296 transition: transform 0.2s, box-shadow 0.2s;
297 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
298 `;
299
300 button.addEventListener('mouseenter', () => {
301 button.style.transform = 'translateY(-2px)';
302 button.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.2)';
303 });
304
305 button.addEventListener('mouseleave', () => {
306 button.style.transform = 'translateY(0)';
307 button.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
308 });
309
310 button.addEventListener('click', runAnalysis);
311
312 document.body.appendChild(button);
313 console.log('分析按鈕已創建');
314 }
315
316 // 初始化
317 function init() {
318 console.log('初始化 Threads Post Analytics...');
319
320 // 等待頁面載入完成
321 if (document.readyState === 'loading') {
322 document.addEventListener('DOMContentLoaded', () => {
323 setTimeout(createAnalysisButton, 2000);
324 });
325 } else {
326 setTimeout(createAnalysisButton, 2000);
327 }
328
329 // 監聽 DOM 變化,確保按鈕始終存在
330 const observer = new MutationObserver(debounce(() => {
331 if (!document.getElementById('threads-analytics-button')) {
332 createAnalysisButton();
333 }
334 }, 1000));
335
336 observer.observe(document.body, {
337 childList: true,
338 subtree: true
339 });
340 }
341
342 // 啟動擴充功能
343 init();
344
345})();