Threads Post Analytics

Analyze Threads posts with engagement metrics and statistics

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})();