TikTok 小助手
// ==UserScript==
// @name TikTok 小助手
// @namespace http://tampermonkey.net/
// @version 5.24
// @description 获取 ttk 数据!
// @author
// @match https://www.tiktok.com/*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_getResourceText
// @grant GM_addStyle
// @icon https://iili.io/dy5xjOg.jpg
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/toastify-js/1.12.0/toastify.min.js
// @resource TOASTIFY_CSS https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css
// ==/UserScript==
(function() {
'use strict';
// 加载 Toastify.js 的 CSS
const toastifyCSS = GM_getResourceText('TOASTIFY_CSS');
GM_addStyle(toastifyCSS);
// 注入按钮样式到页面
injectButtonStyles();
let currentUrl = window.location.href;
let retryCount = 0;
let parsedData = {};
// 获取设置值,默认值为 false
let autoShowDataPanel = GM_getValue('autoShowDataPanel', false);
// 在脚本菜单中添加选项以设置是否自动弹出数据面板
GM_registerMenuCommand('切换自动弹出数据面板', () => {
autoShowDataPanel = !autoShowDataPanel;
GM_setValue('autoShowDataPanel', autoShowDataPanel);
alert(`自动弹出数据面板已${autoShowDataPanel ? '启用' : '禁用'}`);
});
// 注入按钮样式到页面
function injectButtonStyles() {
if (document.getElementById('buttonStyles')) {
return; // Styles already injected
}
const styleElement = document.createElement('style');
styleElement.id = 'buttonStyles';
styleElement.type = 'text/css';
styleElement.textContent = `
.button-85 {
padding: 0.6em 0.8em;
border: none;
outline: none;
color: rgb(255, 255, 255);
background: #111;
cursor: pointer;
position: fixed;
z-index: 10001;
border-radius: 10px;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
}
.button-85:before {
content: "";
background: linear-gradient(
45deg,
#ff0000,
#ff7300,
#fffb00,
#48ff00,
#00ffd5,
#002bff,
#7a00ff,
#ff00c8,
#ff0000
);
position: absolute;
top: -2px;
left: -2px;
background-size: 400%;
z-index: -1;
filter: blur(5px);
-webkit-filter: blur(5px);
width: calc(100% + 4px);
height: calc(100% + 4px);
animation: glowing-button-85 20s linear infinite;
transition: opacity 0.3s ease-in-out;
border-radius: 10px;
}
@keyframes glowing-button-85 {
0% {
background-position: 0 0;
}
50% {
background-position: 400% 0;
}
100% {
background-position: 0 0;
}
}
.button-85:after {
z-index: -1;
content: "";
position: absolute;
width: 100%;
height: 100%;
background: #222;
left: 0;
top: 0;
border-radius: 10px;
}
`;
document.head.appendChild(styleElement);
}
// 创建用于显示数据面板的按钮
function createButton() {
// 创建数据按钮
let dataButton = document.querySelector('#tiktokDataButton');
if (!dataButton) {
dataButton = document.createElement('button');
dataButton.id = 'tiktokDataButton';
dataButton.className = 'button-85';
dataButton.innerHTML = '🤓';
dataButton.style.top = '10px';
dataButton.style.right = '340px';
dataButton.addEventListener('click', () => {
toggleDataDisplay();
});
document.body.appendChild(dataButton);
console.log('Data button created and appended to the page.');
}
// 创建刷新按钮
let refreshButton = document.querySelector('#tiktokRefreshButton');
if (!refreshButton) {
refreshButton = document.createElement('button');
refreshButton.id = 'tiktokRefreshButton';
refreshButton.className = 'button-85';
refreshButton.innerHTML = '🔄';
refreshButton.style.top = '10px';
refreshButton.style.right = '410px';
refreshButton.addEventListener('click', () => {
console.log('Manual refresh button clicked.');
retryCount = 0;
currentUrl = window.location.href;
// 提取数据并在提取完成后更新显示
extractStats(true);
// 更新数据面板内容而不关闭面板
setTimeout(() => {
let dataContainer = document.querySelector('#tiktokDataContainer');
if (dataContainer) {
updateDataContainerContent(dataContainer, parsedData);
} else {
// 如果面板不存在,则显示它
toggleDataDisplay();
}
}, 500); // 添加适当的延迟以确保数据提取完成
});
document.body.appendChild(refreshButton);
console.log('Refresh button created and appended to the page.');
}
}
// 切换数据面板的显示和隐藏
function toggleDataDisplay() {
console.log('toggleDataDisplay called');
let dataContainer = document.querySelector('#tiktokDataContainer');
if (dataContainer) {
// 如果面板已存在,则移除
dataContainer.style.transform = 'translateX(100%)';
dataContainer.style.opacity = '0';
setTimeout(() => {
dataContainer.remove();
}, 500);
return;
}
// 创建新的数据面板
dataContainer = document.createElement('div');
dataContainer.id = 'tiktokDataContainer';
dataContainer.style.transition = 'transform 0.5s ease-in-out, opacity 0.5s ease-in-out';
dataContainer.style.transform = 'translateX(100%)';
dataContainer.style.opacity = '0';
dataContainer.style.position = 'fixed';
dataContainer.style.top = '60px';
dataContainer.style.right = '20px';
dataContainer.style.width = '300px';
dataContainer.style.maxHeight = '400px';
dataContainer.style.overflowY = 'auto';
dataContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
dataContainer.style.border = '1px solid #ccc';
dataContainer.style.borderRadius = '8px';
dataContainer.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.1)';
dataContainer.style.padding = '15px';
dataContainer.style.zIndex = '10000';
dataContainer.style.backdropFilter = 'blur(10px)';
const title = document.createElement('h1');
title.textContent = '🎉 好!发现了🥳';
title.style.color = '#000000';
title.style.marginBottom = '10px';
dataContainer.appendChild(title);
createJsonElement(parsedData, dataContainer);
document.body.appendChild(dataContainer);
setTimeout(() => {
dataContainer.style.transform = 'translateX(0)';
dataContainer.style.opacity = '1';
}, 10);
}
function updateDataContainerContent(container, data) {
// 清空现有内容
container.innerHTML = '';
const title = document.createElement('h1');
title.textContent = '🎉 好!发现了🥳';
title.style.color = '#000000';
title.style.marginBottom = '10px';
container.appendChild(title);
createJsonElement(data, container);
}
// 创建用于显示数据的元素
function createJsonElement(data, container) {
function createDataRow(label, value, copyValue) {
const row = document.createElement('div');
row.style.display = 'flex';
row.style.alignItems = 'center';
row.style.marginBottom = '10px';
const textElement = document.createElement('div');
textElement.textContent = `${label}: ${value}`;
textElement.style.color = '#000000';
const copyIcon = document.createElement('img');
copyIcon.src = base64CopyIcon;
copyIcon.style.cursor = 'pointer';
copyIcon.style.width = '20px';
copyIcon.style.marginLeft = '10px';
copyIcon.addEventListener('click', (event) => {
event.preventDefault();
navigator.clipboard.writeText(copyValue).then(() => {
showNotification('已复制到剪贴板');
}).catch(err => {
console.error('复制失败: ', err);
});
});
row.appendChild(textElement);
row.appendChild(copyIcon);
container.appendChild(row);
}
const accountName = window.location.pathname.split('/')[1].replace('@', '');
const base64CopyIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAYUlEQVR4nGNgGE7Am4GB4QkDA8N/MjFB8JgCw/8TNp4EheQCulvgTWacgILakxgLKImTR8RYAOP7kIhxBvWoBT6jQeQzmor+0zqjoYOhb8Fjahd26MCTTEtAhnsQY8HQAABVctFxfxXV5QAAAABJRU5ErkJggg==";
createDataRow('账户名', accountName, accountName);
createDataRow('粉丝数', data.followerCount || '未知', data.followerCount || '未知');
const fields = {
'diggCount': '点赞数',
'playCount': '播放数',
'commentCount': '评论数',
'shareCount': '分享数',
'collectCount': '收藏数',
'createTime': '创建时间'
};
for (const [field, label] of Object.entries(fields)) {
if (data.hasOwnProperty(field)) {
let value = data[field];
let copyValue = value;
if (field === 'createTime') {
if (value === 0) continue;
const date = new Date(value * 1000);
value = date.toLocaleString();
copyValue = date.toISOString().slice(0, 19).replace('T', ' ');
}
createDataRow(label, value, copyValue);
}
}
}
// 提取视频统计信息
function extractStats(isManual = false) {
fetch(window.location.href)
.then(response => response.text())
.then(responseText => {
const scriptMatch = responseText.match(/<script id="__UNIVERSAL_DATA_FOR_REHYDRATION__" type="application\/json">([\s\S]*?)<\/script>/);
if (scriptMatch) {
try {
const jsonData = JSON.parse(scriptMatch[1]);
const stats = findStats(jsonData);
if (stats) {
parsedData = stats;
extractFollowerCount(() => {
if (autoShowDataPanel) {
toggleDataDisplay();
}
});
} else {
console.warn('未找到视频统计信息。');
}
} catch (e) {
console.error('解析 JSON 时出错:', e);
}
} else {
console.warn('未找到指定的 <script> 标签。');
if (!isManual) {
retryExtractStats();
}
}
createButton(); // 确保按钮被创建
if (isManual) {
showNotification('数据已成功刷新');
}
});
}
// 重试提取数据
function retryExtractStats() {
if (retryCount < 5) {
setTimeout(() => {
console.log('Retrying data extraction...');
retryCount++;
extractStats();
}, 2000);
} else {
console.warn('已达到最大重试次数。数据提取失败。');
}
}
// 提取粉丝数量
function extractFollowerCount(callback) {
const userUrl = `https://www.tiktok.com/${window.location.pathname.split('/')[1]}`;
fetch(userUrl)
.then(response => response.text())
.then(responseText => {
const scriptMatch = responseText.match(/<script id="__UNIVERSAL_DATA_FOR_REHYDRATION__" type="application\/json">([\s\S]*?)<\/script>/);
if (scriptMatch) {
try {
const obj = JSON.parse(scriptMatch[1]);
const followerCount = findFollowerCount(obj);
if (followerCount !== null) {
parsedData.followerCount = followerCount;
if (typeof callback === 'function') {
callback();
}
} else {
console.warn('未找到粉丝计数。');
}
} catch (error) {
console.error('解析 JSON 时出错:', error);
}
} else {
console.log('未找到包含页面数据的 <script> 标签。');
}
})
.catch(error => {
console.error('请求用户页面时出错:', error);
});
}
// 在页面加载完成后运行 extractStats
window.addEventListener('load', () => {
console.log('Page fully loaded, attempting to extract stats.');
extractStats();
});
// 监听 URL 变化并重新运行 extractStats
setInterval(() => {
if (currentUrl !== window.location.href) {
console.log('URL changed, attempting to extract stats again.');
currentUrl = window.location.href;
retryCount = 0;
extractStats();
}
}, 1000);
// 查找视频统计信息
function findStats(jsonData) {
let result = null;
function recursiveSearch(obj) {
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
recursiveSearch(obj[key]);
} else if (['diggCount', 'playCount', 'commentCount', 'shareCount', 'collectCount', 'createTime'].includes(key) && obj[key] !== 0) {
if (!result) {
result = {};
}
result[key] = obj[key];
}
}
}
recursiveSearch(jsonData);
return result;
}
// 查找粉丝数量
function findFollowerCount(jsonData) {
let followerCount = null;
function recursiveSearch(obj) {
for (const key in obj) {
if (key === 'followerCount') {
followerCount = obj[key];
return;
}
if (typeof obj[key] === 'object' && obj[key] !== null) {
recursiveSearch(obj[key]);
}
}
}
recursiveSearch(jsonData);
return followerCount;
}
// 显示通知
function showNotification(message) {
Toastify({
text: message,
duration: 3000,
close: true,
gravity: 'top', // top 或 bottom
position: 'center', // left, center 或 right
style: {
background: getRandomGradientColor(),
color: '#FFFFFF', // 设置文字颜色为白色
borderRadius: '2px',
},
stopOnFocus: true, // 鼠标悬停时停止关闭
}).showToast();
}
// 获取随机的渐变颜色
function getRandomGradientColor() {
const gradients = [
'linear-gradient(to right, rgb(0, 176, 155), rgb(150, 201, 61))',
'linear-gradient(to right, rgb(255,95,109), rgb(255,195,133))',
'linear-gradient(135deg, #73a5ff, #5477fs)'
];
return gradients[Math.floor(Math.random() * gradients.length)];
}
})();